Ghost code
Sometimes, you may need code that will be used only in the proofs/specification, that yet still obeys the typing rules of Rust. In this case, snapshots are not enough, since they don't have lifetimes or ownership.
This is why there exists a separate mechanism: ghost code.
ghost!
and Ghost<T>
are the counterparts of snapshot!
and Snapshot<T>
.
ghost!
blocks
At any point in the code, you may open a ghost!
block:
ghost! {
// code here
}
In ghost!
block, you may write any kind of Rust code, with the following restrictions :
- ghost code must terminate (see termination for details)
- all functions called must have the
#[pure]
attribute - When reading an outer variable, the variable must be of type
Ghost<T>
, or implementCopy
- When writing an outer variable, the variable must be of type
Ghost<T>
orSnapshot<T>
- The output of the
ghost!
block will automatically be wrapped inGhost::new
Those restriction exists to ensure that ghost code is erasable: its presence or absence does not affect the semantics of the actual running program, only the proofs.
Ghost<T>
The Ghost<T>
type is the type of "ghost data". In Creusot, it acts like a Box<T>
, while in normal running code, it is an empty type. It has the same View
as the underlying type, meaning you can use the @
operator directly.
The only restriction of Ghost<T>
is that it may not be dereferenced nor created in non-ghost code.
Examples
- Creating and modifying a ghost variable:
let mut g = ghost!(50);
ghost! {
*g *= 2;
};
proof_assert!(g@ == 100);
- Calling a function in ghost code:
#[pure]
#[requires(*g < i32::MAX)]
#[ensures((^g)@ == (*g)@ + 1)]
fn add_one(g: &mut i32) {
*g += 1;
}
let mut g = ghost!(41);
ghost! {
add_one(&mut *g);
};
proof_assert!(g@ == 42);
- Using a ghost access token:
struct Token;
struct Data {
// ...
}
impl Data {
fn new() -> (Data, Ghost<Token>) { /* */ }
fn read(&self, token: &Ghost<Token>) -> T { /* */ }
fn write(&self, t: T, token: &mut Ghost<Token>) { /* */ }
}