Type Models
Some structs, mostly non-local structs from other crates, contain non-public code which are essential for verification.
To provide a specification for such structs, you can add model fields to be used in specifications. To introduce a type
model for a struct, simply annotate it with the #[model]
macro and declare the to-be-modelled fields inside the
struct:
#[model]
struct SomeStruct {
some_i32: i32,
// ... more fields
}
You then can use the model of SomeStruct
inside specifications via the .model()
function:
#[requires(some_struct.model().some_i32 == 42)]
fn some_method(some_struct: &SomeStruct) {
// ...
}
A model cannot be used outside of specification code, that is the following code will emit an error in Prusti and panic when executed:
fn some_client(some_struct: &mut SomeStruct) {
some_struct.model().some_i32 = 42;
}
This means that a model cannot be instantiated or directly manipulated with runtime code. Instead, the source of a model is always a trusted function or an external specification.
Further remarks
- A model needs to be copyable, i.e. all fields need to be
Copy
. That also applies to type parameters where you need to add theCopy
trait as a bound. - When the modelled type has no fields, a warning will be emmitted. Using
.model()
on such types can lead to unsound verification results. See below for an example.
Modelled types should have fields
Using models on types without fields can have unexpected verification behavior as shown in the code snippet below:
struct A;
// no fields
#[model]
struct A {
val: i32
}
#[trusted]
#[ensures(result.model().val == with_model_val)]
fn create_a(with_model_val: i32) -> A { A {} }
fn main() {
let a1 = create_a(42);
let a2 = create_a(43);
// Can prove: a1.model().val == x for any x
// Can prove: a2.model().val == x for any x
}
The reason for this is due to the encoding of A
in Viper. When encoding the main
function, Prusti creates two
snapshots for a1
and a2
. Since A
has no fields, any two snapshots of A
will be considered equal and thereafter,
their models too. When inhaling the two postconditions for the call to create_a
in main
Viper thus assumes that the
field val
for the same model is 42
and 43
, a contradiction.
Example: std::iter::Iter
Caution
As of yet, iterators are not fully supported in Prusti. This example is a draft and is meant as a possible real-world usage of
#[model]
in the near future.
An example where a type model comes in handy is the std::slice::Iter
struct from the standard library. We would like
to provide a specification for the Iterator
:
impl<T> Iterator<'a, T> for std::slice::Iter<'a, T> {
// ??? spec involving Iter ???
fn next(&mut self) -> Option<&'a T>;
}
There is not really a way to provide a specification for this implementation, because Iter
's fields do not allow
a straightforward specification.
We can instead provide a model for Iter
in the following way, using the #[model]
macro:
use std::slice::Iter;
#[model]
struct Iter<'a, #[generic] T: Copy> {
position: usize,
len: usize,
data: GhostSeq<T> // Note: Assuming this is defined somewhere
}
This allows an instance of Iter<'_, T>
to be modelled by the fields position
, len
and data
.
The model can then be used in specifications:
#[ensures(result.model().position == 0)]
#[ensures(result.model().len == slice.len())]
#[trusted]
fn create_iter<T>(slice: &[T]) -> std::slice::Iter<'_, T> {
slice.iter()
}
#[extern_spec]
impl<T> Iterator for std::slice::Iter<'a, T> {
type Item = T;
[ensures( self .model().data.len == old( self .model().data.len)) )]
#[ensures(old(self.model().pos) < self.model().data.len == >
( result.is_some() &&
result.unwrap() == self.model().data.lookup(old(self.model().pos))))
]
// ... more ...
fn next(&mut self) -> Option<&'a T>;
}