Skip to main content

vstd/
proph.rs

1/*!
2The Verus prophecy library contains two different forms of prophecy variables, each with
3different tradeoffs:
4
5 * [`Prophecy`], loosely based on the prophecy variables from [_The Future is Ours: Prophecy Variables in Separation Logic_](https://plv.mpi-sws.org/prophecies/paper.pdf)
6 * [`ProphecyGhost`], loosely based on the parametric prophecies developed in [_RustHornBelt: A Semantic Foundation for Functional Verification of Rust Programs with Unsafe Code_](https://people.mpi-sws.org/~dreyer/papers/rusthornbelt/paper.pdf), and later used in [_VerusBelt: A Semantic Foundation for Verus’s Proof-Oriented Extensions to the Rust Type System_](https://iris-project.org/pdfs/2026-pldi-verusbelt.pdf).
7
8# Tradeoffs
9
10The two types provide a similar interface, but make different tradeoffs along certain dimensions:
11
121. [`ProphecyGhost`] can resolve to any ghost-computed value, but [`Prophecy`] is required to resolve to an exec-computed value.
132. [`ProphecyGhost`] allows prophecies to resolve to values dependent on other prophecies, whereas [`Prophecy`] does not allow this.
143. The [`ProphecyGhost::value`] function, which gives the prophesied value, is marked
15   [`verifier::prophetic`](https://verus-lang.github.io/verus/guide/reference-attributes.html#verifierrprophetic), which means it is subject to Verus's prophecy-dependence tracking, which entails certain restrictions on how it is used. However, [`Prophecy::view`] is (counterintuitively) NOT marked prophetic, and so it is not subject to the restrictions that Verus usually enforces on prophesied values. (This is presently the only way to get a prophecy value without being subject to prophecy-dependence restrictions.)
16
17|                   | Ghost resolution | Dependent resolution | Prophecy-dependence tracking |
18|-------------------|------------------|----------------------|------------------------------|
19| [`Prophecy`]      | ✘ Exec only      | ✘ Not allowed        | ✔ No enforcement             |
20| [`ProphecyGhost`] | ✔ Allowed        | ✔ Allowed            | ✘ Enforced                   |
21
22Why is prophecy-dependence tracking necessary, and how does `Prophecy` avoid the need for it?
23The reason for these tradeoffs have to do with two specific soundness issues which must be avoided.
24
25## Soundness issue 1: Cyclically dependent prophecy variables
26
27We need to make sure a prophecy value cannot resolve to a value dependent on itself, as such a cyclic dependency can easily create an inconsistency.
28
29```rust
30use vstd::proph::ProphecyGhost;
31
32proof fn test() {
33    let tracked p = ProphecyGhost::<bool>::new();
34    p.resolve(!p.value());
35    assert(false); // contradiction!
36}
37```
38
39`ProphecyGhost` avoids this because `p.value()` is considered a prophetic value which is subject to Verus's dependency tracking.
40
41```
42error: prophetic value not allowed for argument to proof-mode function with tracked parameters
43 --> cyclic.rs:9:15
44  |
459 |     p.resolve(!p.value());
46  |               ^---------
47  |               ||
48  |               |the result of this call is prophetic
49  |               this argument is expected to be non-prophetic
50  |
51```
52
53On the other hand, `Prophecy` does not have this problem because its `resolve` function requires its resolved value to be computed in exec code.
54By definition, then, the resolution cannot be dependent on prophesied values, which are always ghost.
55We can argue for soundness similarly to the [_Future is Ours_ paper]((https://plv.mpi-sws.org/prophecies/paper.pdf)):
56for any program trace, we can determine the full sequence of resolved prophecy variables before instantiating
57the proof accompanying the program.
58
59## Soundness issue 2: Unbounded prophecies and termination checking
60
61When working with prophecy variables that depend on other prophecy variables, it possible to end up with data
62which is not bounded across all possible program traces. Such prophetic information cannot be used for any kind
63of termination reasoning.
64
65For example, consider the [`ProphecySeq`] library, which is implemented via prophecy dependent variables from [`ProphecyGhost`].
66For a prophetic sequence `s`, we can resolve the first element to `x` and get another prophetic sequence `s'` such that `s == [x] + s'`.
67We can do this indefinitely; thus, there may be an infinite program trace, for which any possible value of `s` is contradictory.
68This is fine for safety reasoning, where we only need to consider finite program traces, but not for liveness reasoning.
69
70For example, using `ProphecySeq`, we can easily write an infinite loop with a `decreases`-measure that apparently decreases forever:
71
72```
73use vstd::proph::ProphecySeq;
74
75fn test() {
76    let tracked s = ProphecySeq::<int>::new();
77    loop
78        decreases s.seq().len()
79    {
80        // let ghost old_s = s.seq();
81        proof { s.resolve_cons(0); }
82        // assert(old_s == [0] + s.seq());
83    }
84}
85```
86
87Again, `ProphecyGhost` (and thus `ProphecySeq`) avoid this soundness issue through prophecy-dependence tracking,
88which detects that the `decreases` clause is illegal because it depends on a prophetic value:
89
90```
91error: prophetic value not allowed for 'decreases' clause
92  --> infinite_loop.rs:10:19
93   |
9410 |         decreases s.seq().len()
95   |                   -------^^^^^^
96   |                   |
97   |                   the result of this call is prophetic
98   |                   this decreases-measure is expected to be non-prophetic
99```
100
101`Prophecy`, on the other hand, simply does not support prophecy-dependent resolutions, so it does not have this problem.
102This is why there is no equivalent of `ProphecySeq` for the `Prophecy`-style of prophecy variable.
103(Note that while the _Future is Ours_ paper does support prophetic sequences, they only consider partial correctness,
104not termination reasoning, and so they don't have this problem.)
105
106# Relationship to mutable borrows
107
108Verus uses a prophecy-based encoding for mutable borrows, where the "final value" of a mutable
109reference `x: &mut T`, denoted `*final(x)` is a prophesied value. Prophecy variables with
110mutable borrows behave most similarly to [`ProphecyGhost`]:
111
1121. Mutable borrows allow ghost-dependent resolution, thus supporting `&mut Ghost<T>` and `&mut Tracked<T>`.
113
1142. Mutable borrows allow prophecy-dependent resolution, as demonstrated by reborrowing:
115
116```rust
117fn get_mut_fst<A, B>(pair: &mut (A, B)) -> (fst: &mut A)
118    ensures
119        *fst == old(pair).0,
120        *final(pair) == (*final(fst), old(pair).1),
121{
122    &mut pair.0
123}
124```
125
126The prophetic value `*final(pair)` is resolved to a value dependent on prophetic value `*final(fst)`.
127
1283. The value `*final(x)` is subject to prophecy-dependence tracking:
129
130```
131fn test(x: &mut u64) {
132    let xfinal = Ghost(*final(x));
133}
134```
135
136```
137error: prophetic value not allowed for 'Ghost' wrapper
138 --> mut_ref_test.rs:6:24
139  |
1406 |     let xfinal = Ghost(*final(x));
141  |                        ^^^^^^^^^
142  |                        |
143  |                        the `final` builtin is prophetic
144  |                        operand of this wrapper is expected to be non-prophetic
145```
146*/
147#![allow(unused_variables)]
148
149use super::modes::*;
150use super::prelude::*;
151
152verus! {
153
154/** A general-purpose prophecy variable.
155
156A prophecy variable is allocated via [`Prophecy::new`](Self::new)
157and resolved to a definitive value via [`Prophecy::resolve`](Self::resolve).
158
159In contrast to the similar [`ProphecyGhost`], this does not require us to mark the
160[`value()`](Self::view) as [`verifier::prophetic`](https://verus-lang.github.io/verus/guide/reference-attributes.html#verifierprophetic), which can make it easier to use.
161However, it does not allow dependent resolutions, and it can only resolve to values
162which are computed purely in exec-mode (as enforced by the `T: Structural` bound).
163See [the module level documentation](self) for a detailed comparison with the rationale
164for these trade-offs.
165*/
166#[verifier::external_body]
167#[verifier::accept_recursive_types(T)]
168pub struct Prophecy<T> {
169    v: Ghost<T>,
170}
171
172impl<T> Prophecy<T> where T: Structural {
173    /// The prophecized value.
174    pub uninterp spec fn view(self) -> T;
175
176    /// Allocate a new prophecy variable.
177    #[inline(always)]
178    #[verifier::external_body]
179    pub exec fn new() -> (proph_var: Self) {
180        Prophecy::<T> { v: Ghost::assume_new() }
181    }
182
183    /// Resolve the prophecy variable to a concrete value.
184    /// This consumes `self`, so it can only be called once.
185    #[inline(always)]
186    #[verifier::external_body]
187    pub exec fn resolve(self, v: &T)
188        ensures
189            self@ == v,
190    {
191    }
192}
193
194/** A general-purpose prophecy variable.
195
196A prophecy variable is allocated via [`ProphecyGhost::new`](Self::new)
197and resolved to a definitive value via [`ProphecyGhost::resolve`](Self::resolve).
198
199In contrast to the similar [`Prophecy`], this allows resolving on ghost computations
200and permits dependent resolutions. However, the [`value()`](Self::value)
201is marked [`verifier::prophetic`](https://verus-lang.github.io/verus/guide/reference-attributes.html#verifierrprophetic), which entails certain restrictions in the way it can be used.
202See [the module level documentation](self) for a detailed comparison with the rationale
203for these trade-offs.
204
205See [`ProphecySeq`] for an example of a library verified using dependent resolutions.
206*/
207
208#[verifier::external_body]
209#[verifier::accept_recursive_types(T)]
210pub tracked struct ProphecyGhost<T> {
211    _t: Ghost<T>,
212}
213
214impl<T> ProphecyGhost<T> {
215    #[verifier::prophetic]
216    pub uninterp spec fn value(&self) -> T;
217
218    pub axiom fn new() -> (tracked proph_var: Self);
219
220    pub axiom fn resolve(tracked self, value: T)
221        ensures
222            self.value() == value,
223    ;
224
225    pub proof fn resolve_dependent<U>(
226        tracked self,
227        tracked u: &ProphecyGhost<U>,
228        f: spec_fn(U) -> T,
229    )
230        ensures
231            self.value() == f(u.value()),
232    {
233        self.resolve_dependent_2(u, &ProphecyGhost::new(), |u: U, v: ()| f(u));
234    }
235
236    pub axiom fn resolve_dependent_2<U, V>(
237        tracked self,
238        tracked u: &ProphecyGhost<U>,
239        tracked v: &ProphecyGhost<V>,
240        f: spec_fn(U, V) -> T,
241    )
242        ensures
243            self.value() == f(u.value(), v.value()),
244    ;
245
246    pub proof fn resolve_dependent_3<U, V, W>(
247        tracked self,
248        tracked u: &ProphecyGhost<U>,
249        tracked v: &ProphecyGhost<V>,
250        tracked w: &ProphecyGhost<W>,
251        f: spec_fn(U, V, W) -> T,
252    )
253        ensures
254            self.value() == f(u.value(), v.value(), w.value()),
255    {
256        let tracked vw = ProphecyGhost::<(V, W)>::new();
257        self.resolve_dependent_2(u, &vw, |u: U, vw: (V, W)| f(u, vw.0, vw.1));
258        vw.resolve_dependent_2(v, w, |v: V, w: W| (v, w));
259    }
260}
261
262/// A prophetic sequence, which permits prophesying one element at a time.
263pub tracked struct ProphecySeq<T> {
264    tracked var: ProphecyGhost<Seq<T>>,
265}
266
267impl<T> ProphecySeq<T> {
268    #[verifier::prophetic]
269    pub closed spec fn seq(&self) -> Seq<T> {
270        self.var.value()
271    }
272
273    pub proof fn new() -> (tracked proph_var: Self) {
274        ProphecySeq { var: ProphecyGhost::new() }
275    }
276
277    pub proof fn resolve_cons(tracked &mut self, v: T)
278        ensures
279            old(self).seq() == seq![v] + final(self).seq(),
280    {
281        let tracked mut var = ProphecyGhost::new();
282        tracked_swap(&mut var, &mut self.var);
283        var.resolve_dependent(&self.var, |w| seq![v] + w);
284    }
285
286    pub proof fn resolve_nil(tracked self)
287        ensures
288            self.seq() == seq![],
289    {
290        let tracked ProphecySeq { var } = self;
291        var.resolve(seq![]);
292    }
293}
294
295} // verus!