Data structures like RefCell mutate internal state on methods like borrow and borrow_mut. This state is not part of the public API and therefore cannot be mentioned in the modifies clause without breaking encapsulation.
Consider the following example that shows a simple function modifying a RefCell with a modifies clause that should be valid given the body of the function. It overapproximates the function (as the result will be an arbitrary value rather than the concrete 100 but this is not the point).
#[kani::modifies(cell.borrow())]
fn modifies_ref_cell(cell: &RefCell<u32>) {
*cell.try_borrow_mut().map(|r| *r = 100);
}
However running this with kani creates the error:
Check 60: std::ptr::write::<isize>.assigns.3
- Status: FAILURE
- Description: "Check that *dst is assignable"
The reason is that as cell is borrowed it internally mutably sets the BorrowFlag (which is an isize) stored in the RefCell.
In Rust's encapsulation fields (like borrow) in the RefCell should not be used or referenced by a user of RefCell. because they are considered potentially unstable implementation details. It would be breaking the encapsulation if the modifies clause needed to reference the internal fields of the cell for the contract to pass. At the same time we can't simply assume that all private fields may be modified because that would cause them to be havocked which will break the invariants wrt the borrow field. For instance in this example cell.borrow == old(cell.borrow) (instead of kani::any()).
In contrast if we had the function
#[kani::modifies(cell.borrow())]
fn modifies_ref_cell_2(cell: &'a RefCell<u32>) -> Option<RefMut<'a, u32>> {
*cell.try_borrow_mut().ok().map(|r| {
*r = 100;
r
});
}
Then if return.is_some() { cell.borrow == 1 } else { cell.borrow == old(cell.borrow) }.
This is to illustrate that what it means to be assignable or assigned to is different for some types, such as RefCell.
An additional issue is that structures such as RefCell, Rc, Arc etc also perform assignment even when not used mutably. A call to RefCell::borrow or Rc::clone while nominally being immutable operations at the surface will perform assignment to internal fields.
Data structures like
RefCellmutate internal state on methods likeborrowandborrow_mut. This state is not part of the public API and therefore cannot be mentioned in themodifiesclause without breaking encapsulation.Consider the following example that shows a simple function modifying a
RefCellwith amodifiesclause that should be valid given the body of the function. It overapproximates the function (as the result will be an arbitrary value rather than the concrete100but this is not the point).However running this with kani creates the error:
The reason is that as
cellis borrowed it internally mutably sets theBorrowFlag(which is anisize) stored in theRefCell.In Rust's encapsulation fields (like
borrow) in theRefCellshould not be used or referenced by a user ofRefCell. because they are considered potentially unstable implementation details. It would be breaking the encapsulation if themodifiesclause needed to reference the internal fields of the cell for the contract to pass. At the same time we can't simply assume that all private fields may be modified because that would cause them to be havocked which will break the invariants wrt theborrowfield. For instance in this examplecell.borrow == old(cell.borrow)(instead ofkani::any()).In contrast if we had the function
Then
if return.is_some() { cell.borrow == 1 } else { cell.borrow == old(cell.borrow) }.This is to illustrate that what it means to be assignable or assigned to is different for some types, such as
RefCell.An additional issue is that structures such as
RefCell,Rc,Arcetc also perform assignment even when not used mutably. A call toRefCell::borroworRc::clonewhile nominally being immutable operations at the surface will perform assignment to internal fields.