Skip to content

Commit

Permalink
Expand docs to illustrate how the modes work together (#1377)
Browse files Browse the repository at this point in the history
  • Loading branch information
parno authored Jan 16, 2025
1 parent b7cf26c commit 716aaa5
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 0 deletions.
1 change: 1 addition & 0 deletions source/docs/guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- [spec functions vs. proof functions, recommends](spec_vs_proof.md)
- [Ghost code vs. exec code](ghost_vs_exec.md)
- [const declarations](const.md)
- [Putting it all together](triangle.md)
- [Recursion and loops](recursion_loops.md)
- [Recursive spec functions, decreases, fuel](recursion.md)
- [Recursive exec and proof functions, proofs by induction](induction.md)
Expand Down
47 changes: 47 additions & 0 deletions source/docs/guide/src/triangle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Putting It All Together

To show how `spec`, `proof`, and `exec` code work together, consider the example
below of computing the n-th [triangular number](https://en.wikipedia.org/wiki/Triangular_number).
We'll cover this example and the features it uses in more detail in [Chapter 4](recursion_loops.md),
so for now, let's focus on the high-level structure of the code.

We use a `spec` function `triangle` to mathematically define our specification using natural numbers (`nat`)
and a recursive description. We then write a more efficient iterative implementation
as the `exec` function `loop_triangle` (recall that `exec` is the default mode for functions).
We connect the correctness of `loop_triangle`'s return value to our mathematical specification
in `loop_triangle`'s `ensures` clause.

However, to successfully verify `loop_triangle`, we need a few more things. First, in executable
code, we have to worry about the possibility of arithmetic overflow. To keep things simple here,
we add a precondition to `loop_triangle` saying that the result needs to be less than one million,
which means it will certainly fit into a `u32`.

We also need to translate the knowledge that the final `triangle` result fits in a `u32`
into the knowledge that each individual step of computing the result won't overflow,
i.e., that computing `sum = sum + idx` won't overflow. We can do this by showing
that `triangle` is monotonic; i.e., if you increase the argument to `triangle`, the result increases too.
Showing this property requires an [inductive proof](induction.md). We cover inductive proofs
later; the important thing here is that we can do this proof using a `proof` function
(`triangle_is_monotonic`). To invoke the results of our proof in our `exec` implementation,
we [assert](proof_functions.md#assert-by) that the new sum fits, and as
justification, we an invoke our proof with the relevant arguments. At the
call site, Verus will check that the preconditions for `triangle_is_monotonic`
hold and then assume that the postconditions hold.

Finally, our implementation uses a while loop, which means it requires some [loop invariants](while.md),
which we cover later.

```rust
{{#include ../../../rust_verify/example/guide/recursion.rs:spec}}
```

```rust
{{#include ../../../rust_verify/example/guide/recursion.rs:mono}}
```

```rust
{{#include ../../../rust_verify/example/guide/recursion.rs:loop}}
```



0 comments on commit 716aaa5

Please sign in to comment.