The option
strategy
The option
strategy can be applied to fields of type Option<V>
for any type V
.
fields {
#[sharding(option)]
pub field: Option<V>,
}
This creates a token type, State::field
, which a field value: V
.
When field
is None
, this corresponds to no token existing, while
when field
is Some(v)
, this corresponds to a token of value v
existing.
Having multiple such tokens at the same time is an impossible state.
Quick Reference
Command | Meaning in transition | Exchange Fn Parameter |
---|---|---|
init field = v_opt; |
init field = v_opt; |
Output Option<State::field> |
remove field -= Some(v); |
require field === Some(v); update field = None; |
Input State::field |
have field >= Some(v); |
require field === Some(v); |
Input &State::field |
add field += Some(v); |
assert field === None; update field = Some(v); |
Output State::field |
remove field -= (v_opt); |
require v_opt === None || field === v_opt;
update field = if v_opt === None { field }
else { None }; |
Input Option<State::field> |
have field >= (v_opt); |
require v_opt === None || field === v_opt; |
Input &Option<State::field> |
add field += (v_opt); |
assert field === None || v_opt === None;
update field = if v_opt === None { field }
else { v_opt }; |
Output Option<State::field> |
Initializing the field
Initializing the field is done with the usual init
statement (as it for all strategies).
init field = opt_v;
The instance-init function will return a token of type Option<State::field>
,
related as follows:
value of opt_v: V |
value of optional token Option<State::field> |
---|---|
None |
None |
Some(v) |
Some(tok) where tok@.value === v |
Adding a token
To write an operation that creates a token with value v
,
equivalently,
updating the field’s value from None
to Some(v)
, write, inside any transition!
operation:
add field += Some(v);
This operation has an inherent safety condition that the prior value of field
is None
.
The resulting token exchange function will return a token of type State::field
and with value v
.
If you require manual proof to prove the inherent safety condition, you can add
an optional by
clause:
add field += Some(v)
by {
// proof goes here
};
Removing a token
To write an operation that removes a token with value v
,
equivalently,
updating the field’s value from Some(v)
to None
, write, inside any transition!
operation:
remove field -= Some(v);
The resulting exchange function will consume a State::field
token with value v
as a parameter.
Instead of specifying v
as an exact expression, you can also pattern-match
by using the let
keyword.
remove field -= Some(let $pat);
This will require the prior value of field
to match Some($pat)
,
and this statement binds all the variables in $pat
for use later in the transition.
Checking the value of the token
To check the value of the token without removing it,
write, inside any transition!
, readonly!
or property!
operation:
have field >= Some(v);
The resulting exchange function will accept an immutable reference
&State::field
(that is, it takes the token as input but does not consume it).
Instead of specifying v
as an exact expression, you can also pattern-match
by using the let
keyword.
have field >= Some(let $pat);
This will require the prior value of field
to match Some($pat)
,
and this statement binds all the variables in $pat
for use later in the transition.
Updating a token
To update the value of an option
token, first remove
it, then add
it,
in sequence.
remove field -= Some(let _);
add field += Some(new_v);
Operations that manipulate optional tokens
You can also write versions of the above operations that operate on optional tokens.
These operations are equivalent to above versions whenever opt_v = Some(v)
,
and they are all no-ops when opt_v = None
.
To create an Option<State::field>
:
add field += (opt_v);
To consume an Option<State::field>
:
remove field -= (opt_v);
To check the value of an Option<State::field>
:
have field >= (opt_v);
The value of opt_v
is related to the value of Option<State::field>
as they are for initialization.
Example
tokenized_state_machine!{ State {
fields {
#[sharding(variable)]
pub token_exists: bool,
#[sharding(option)]
pub field: Option<int>,
}
#[invariant]
pub fn token_exists_correct(&self) -> bool {
self.token_exists <==> self.field.is_Some()
}
init!{
initialize(v: int) {
init field = Option::Some(v);
init token_exists = true;
}
}
transition!{
add_token(v: int) {
require !pre.token_exists;
update token_exists = true;
add field += Some(v);
}
}
transition!{
remove_token() {
remove field -= Some(let _);
assert pre.token_exists;
update token_exists = false;
}
}
transition!{
have_token() {
have field >= Some(let _);
assert pre.token_exists;
}
}
#[inductive(initialize)]
fn initialize_inductive(post: Self, v: int) { }
#[inductive(add_token)]
fn add_token_inductive(pre: Self, post: Self, v: int) { }
#[inductive(remove_token)]
fn remove_token_inductive(pre: Self, post: Self) { }
#[inductive(have_token)]
fn have_token_inductive(pre: Self, post: Self) { }
}}
proof fn option_example() {
#[verifier::proof]
let (Tracked(instance), Tracked(mut token_exists), Tracked(token_opt)) =
State::Instance::initialize(5);
#[verifier::proof]
let token = token_opt.tracked_unwrap();
assert(token@.value == 5);
instance.have_token(&token_exists, &token);
assert(token_exists@.value == true);
instance.remove_token(&mut token_exists, token); // consumes token
assert(token_exists@.value == false); // updates token_exists to `false`
#[verifier::proof]
let token = instance.add_token(19, &mut token_exists);
assert(token_exists@.value == true); // updates token_exists to `true`
assert(token@.value == 19); // new token has value 19
}