-
Quoting from an email conversation (with permission) with @boulme:
The issue was figuring out how to specify a function which didn't call the closure and ensure that we can prove that any mutable captured variables are changed. extern crate creusot_contracts;
use creusot_contracts::*;
#[ensures(run==old(run))]
fn skip<F:FnOnce() -> ()>(run: F) {
}
#[ensures(result == 0i32)]
fn example() -> i32 {
let mut r = 0i32;
let run =
#[ensures (r == 2i32)]
|| { r = 2 };
skip(run);
r
}
fn main(){
example();
} |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment
-
In short the answer is to add The full answer is a bit longer. Unlike many traditional verification tools, Creusot doesn't really have a notion of 'post-state', it views Rust functions as affine functions which consume their arguments, meaning If we write a function Naturally we can ask ourselves how this can generalize to an arbitrary type, potentially containing mutable borrows (and thus propheties)? well we do it via the In the case of |
Beta Was this translation helpful? Give feedback.
In short the answer is to add
#[ensures(run.resolve())]
to the specification ofskip
.The full answer is a bit longer.
Unlike many traditional verification tools, Creusot doesn't really have a notion of 'post-state', it views Rust functions as affine functions which consume their arguments, meaning
run
no longer exists after the function call.The only way to have inter-procedure mutations / side-effects is through
&mut
and thus through propecies.If we write a function
fn drop_mut(x :&mut u32) { }
, we specify thatx
is unchanged by adding the postcondition^x == *x
.Naturally we can ask ourselves how this can generalize to an arbitrary type, potentially containing mutable borrows (and t…