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!