Skip to content

Commit 911e4e7

Browse files
authored
Add documentation for traits (#2537)
1 parent d287ef8 commit 911e4e7

3 files changed

Lines changed: 361 additions & 1 deletion

File tree

examples/guide/traits.rs

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// rust_verify/tests/example.rs expect-success
2+
use vstd::prelude::*;
3+
4+
verus! {
5+
6+
// ANCHOR: basic_trait
7+
trait Compressor {
8+
fn compress(&self, input: u64) -> (output: u64)
9+
ensures output <= input;
10+
}
11+
// ANCHOR_END: basic_trait
12+
13+
// ANCHOR: impl_extends
14+
struct HalfCompressor;
15+
16+
impl Compressor for HalfCompressor {
17+
// The trait's `ensures output <= input` is automatically inherited.
18+
// We additionally specify the exact return value.
19+
fn compress(&self, input: u64) -> (output: u64)
20+
ensures output == input / 2,
21+
{
22+
input / 2
23+
}
24+
}
25+
// ANCHOR_END: impl_extends
26+
27+
// ANCHOR: dispatch
28+
fn compress_generic<C: Compressor>(c: &C, x: u64) {
29+
let r = c.compress(x);
30+
assert(r <= x); // OK: trait-level ensures holds for every C
31+
}
32+
33+
fn compress_concrete(c: &HalfCompressor, x: u64) {
34+
let r = c.compress(x);
35+
assert(r <= x); // From the trait ensures
36+
assert(r == x / 2); // From HalfCompressor's stronger ensures (statically resolved)
37+
}
38+
// ANCHOR_END: dispatch
39+
40+
// ANCHOR: requires_ensures
41+
trait Bounded {
42+
fn clamp(&self, val: u64, lo: u64, hi: u64) -> (result: u64)
43+
requires lo <= hi,
44+
ensures lo <= result <= hi;
45+
}
46+
// ANCHOR_END: requires_ensures
47+
48+
// ANCHOR: requires_ensures_impl
49+
struct Saturate;
50+
51+
impl Bounded for Saturate {
52+
// Inherits: requires lo <= hi, ensures lo <= result <= hi
53+
// Adds: the exact formula for result
54+
fn clamp(&self, val: u64, lo: u64, hi: u64) -> (result: u64)
55+
ensures result == if val < lo { lo } else if val > hi { hi } else { val },
56+
{
57+
if val < lo { lo } else if val > hi { hi } else { val }
58+
}
59+
}
60+
// ANCHOR_END: requires_ensures_impl
61+
62+
// ANCHOR: spec_trait
63+
trait Distance {
64+
spec fn dist(&self, other: &Self) -> nat;
65+
66+
fn distance(&self, other: &Self) -> (d: u64)
67+
ensures
68+
d as nat == self.dist(other),
69+
;
70+
71+
proof fn valid_distance_metric()
72+
ensures
73+
forall |x: &Self, y| x.dist(y) == y.dist(x),
74+
forall |x: &Self, y| x.dist(y) == 0 <==> x == y,
75+
forall |x: &Self, y, z| x.dist(y) <= x.dist(z) + z.dist(y),
76+
;
77+
}
78+
// ANCHOR_END: spec_trait
79+
80+
// ANCHOR: spec_trait_impl
81+
struct P {
82+
u: u64
83+
}
84+
85+
impl Distance for P {
86+
spec fn dist(&self, other: &Self) -> nat {
87+
vstd::math::abs(self.u - other.u)
88+
}
89+
90+
fn distance(&self, other: &Self) -> u64 {
91+
if self.u > other.u {
92+
self.u - other.u
93+
} else {
94+
other.u - self.u
95+
}
96+
}
97+
98+
proof fn valid_distance_metric()
99+
{
100+
}
101+
}
102+
// ANCHOR_END: spec_trait_impl
103+
104+
// ANCHOR: view_impl
105+
struct Stack {
106+
data: Vec<u64>,
107+
}
108+
109+
impl View for Stack {
110+
type V = Seq<u64>;
111+
112+
closed spec fn view(&self) -> Seq<u64> {
113+
self.data@
114+
}
115+
}
116+
117+
impl Stack {
118+
fn push(&mut self, val: u64)
119+
ensures final(self)@ == old(self)@.push(val),
120+
{
121+
self.data.push(val);
122+
}
123+
124+
fn is_empty(&self) -> (result: bool)
125+
ensures result <==> self@.len() == 0,
126+
{
127+
self.data.len() == 0
128+
}
129+
}
130+
// ANCHOR_END: view_impl
131+
132+
// ANCHOR: default_ensures
133+
trait Reducer {
134+
// Every implementation must satisfy: output <= input.
135+
// The default implementation additionally satisfies: output == input / 2.
136+
fn halve(&self, input: u64) -> (output: u64)
137+
ensures output <= input,
138+
default_ensures output == input / 2,
139+
{
140+
input / 2
141+
}
142+
}
143+
// ANCHOR_END: default_ensures
144+
145+
// ANCHOR: default_ensures_impls
146+
struct DefaultReducer;
147+
148+
impl Reducer for DefaultReducer {
149+
// No override: inherits the default implementation and its default_ensures.
150+
}
151+
152+
struct ThirdReducer;
153+
154+
impl Reducer for ThirdReducer {
155+
// Overrides with a different strategy. Only the trait `ensures` (output <= input)
156+
// applies to callers who don't statically know the type.
157+
fn halve(&self, input: u64) -> (output: u64)
158+
ensures output == input / 3,
159+
{
160+
input / 3
161+
}
162+
}
163+
// ANCHOR_END: default_ensures_impls
164+
165+
// ANCHOR: default_ensures_callers
166+
fn call_generic<H: Reducer>(h: &H, x: u64) {
167+
let r = h.halve(x);
168+
assert(r <= x); // From trait ensures — always available
169+
// assert(r == x / 2); // Would FAIL: not known for arbitrary H
170+
}
171+
172+
fn call_default(h: &DefaultReducer, x: u64) {
173+
let r = h.halve(x);
174+
assert(r <= x); // From trait ensures
175+
assert(r == x / 2); // From default_ensures (DefaultReducer uses the default impl)
176+
}
177+
178+
fn call_override(h: &ThirdReducer, x: u64) {
179+
let r = h.halve(x);
180+
assert(r <= x); // From trait ensures
181+
assert(r == x / 3); // From ThirdReducer's own ensures
182+
// assert(r == x / 2); // Would FAIL: ThirdReducer overrides, no default_ensures
183+
}
184+
// ANCHOR_END: default_ensures_callers
185+
186+
} // verus!

source/docs/guide/src/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
- [Mutation, references, and borrowing](mutation-references-borrowing.md)
8181
- [Mutable references](mutable-references.md)
8282
- [Assertions about mutable references](assert-mut-ref.md)
83-
- [Traits]()
83+
- [Traits](traits.md)
8484
- [Iterator Specifications](./iterator-specs.md)
8585
- [Higher-order executable functions](./higher-order-fns.md)
8686
- [Passing functions as values](./exec_funs_as_values.md)

source/docs/guide/src/traits.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
# Traits
2+
3+
Verus supports writing specifications for trait functions, including `requires` and
4+
`ensures` clauses. Specifications on a trait function serve as a *contract* that all
5+
implementations must satisfy, and that callers can rely on when they call the function
6+
through a trait bound.
7+
8+
For traits defined in external crates (e.g., from the Rust standard library), see
9+
[External trait specifications](./external_trait_specifications.md).
10+
11+
## Trait function specifications
12+
13+
Trait functions can have `requires` and `ensures` clauses just like ordinary functions:
14+
15+
```rust
16+
{{#include ../../../../examples/guide/traits.rs:basic_trait}}
17+
```
18+
19+
Any type implementing `Compressor` must provide a `compress` function whose body
20+
satisfies `output <= input`.
21+
22+
## Extending specifications in implementations
23+
24+
An implementation automatically inherits all `requires` and `ensures` clauses from the
25+
trait declaration. Additionally:
26+
27+
* An `impl` **can** add stronger `ensures` clauses.
28+
* An `impl` **cannot** add new `requires` clauses — that would be unsound, because a
29+
caller using the trait bound `C: Compressor` has no obligation to satisfy
30+
requirements that the trait doesn't mention.
31+
32+
```rust
33+
{{#include ../../../../examples/guide/traits.rs:impl_extends}}
34+
```
35+
36+
`HalfCompressor::compress` inherits `ensures output <= input` from the trait.
37+
Verus verifies that the function's body satisfies both the inherited postcondition
38+
and the additional postcondition added by the implementation (`ensures output == input / 2`).
39+
40+
A trait function can also include both `requires` and `ensures`:
41+
42+
```rust
43+
{{#include ../../../../examples/guide/traits.rs:requires_ensures}}
44+
```
45+
46+
```rust
47+
{{#include ../../../../examples/guide/traits.rs:requires_ensures_impl}}
48+
```
49+
50+
## Generic vs. concrete dispatch
51+
52+
When Verus can statically determine the concrete type of a trait function call, it uses
53+
the possibly-stronger specification from the `impl`. When the call is through a generic
54+
type parameter, Verus only knows the trait-level specification.
55+
56+
```rust
57+
{{#include ../../../../examples/guide/traits.rs:dispatch}}
58+
```
59+
60+
## Spec and proof functions
61+
62+
A trait may also contain `spec` and `proof` function declarations.
63+
For example, the trait below requires implementations to provide
64+
a distance metric (`dist`) as a specification function and then to prove that
65+
their `distance` function actually computes that metric.
66+
Moreover, the implementation must also prove (in its body for `valid_distance_metric`)
67+
that `dist` is a reasonable metric.
68+
69+
```rust
70+
{{#include ../../../../examples/guide/traits.rs:spec_trait}}
71+
```
72+
73+
Here's an example of an implementation of our `Distance` trait:
74+
```rust
75+
{{#include ../../../../examples/guide/traits.rs:spec_trait_impl}}
76+
```
77+
In this case, Verus can automatically prove all of the postconditions
78+
for `valid_distance_metric`, but a more complex distance metric might
79+
need additional proof annotations.
80+
81+
Note that it's not necessary to repeat the requires/ensures that
82+
the implementation inherits from the trait definition.
83+
84+
85+
## The `View` trait
86+
87+
The most commonly used trait in `vstd` is `View`. It allows users to give
88+
executable types a mathematical abstraction (`type V`) accessed via the `view`
89+
function. Since this is such a commonly invoked function, Verus provides the
90+
[`@` operator](reference-at-sign.md) as a shortcut, so that you can write `x@`
91+
instead of `x.view()`.
92+
93+
```rust
94+
pub trait View {
95+
type V;
96+
spec fn view(&self) -> Self::V;
97+
}
98+
```
99+
100+
`vstd` provides `View` implementations for common types:
101+
- `Vec<T>``Seq<T>`
102+
- `HashMap<K, V>``Map<K, V>`
103+
- `HashSet<K>``Set<K>`
104+
- Primitive types (`u64`, `bool`, etc.) → themselves
105+
106+
To implement `View` for a custom type, choose an appropriate abstraction and
107+
then define `type V` and `spec fn view`:
108+
109+
```rust
110+
{{#include ../../../../examples/guide/traits.rs:view_impl}}
111+
```
112+
113+
Because `Stack`'s `data` is a private field, `view` is `closed` — callers cannot see its
114+
definition, but they can still reason about the effect each function has on that view,
115+
as illustrated by the postconditions on `push` and `is_empty`. If you want
116+
callers to unfold `@` to its definition (e.g., for a `pub` type with a `pub`
117+
field), use `open spec fn view` instead.
118+
119+
`vstd` also provides `DeepView`, which recursively applies the view abstraction
120+
to nested elements. Most code only needs `View`.
121+
122+
## `default_ensures`: specifications for default function implementations
123+
124+
In Rust, a trait function can provide a *default implementation* — a body that
125+
implementations inherit if they do not override the function. This creates a subtle
126+
specification problem: the trait-level `ensures` clause must be weak enough to allow
127+
any valid override, but the default body may satisfy a *stronger* postcondition.
128+
129+
`default_ensures` solves this by separating these two concerns:
130+
131+
```rust
132+
{{#include ../../../../examples/guide/traits.rs:default_ensures}}
133+
```
134+
135+
Here the **trait-level `ensures`** (`output <= input`) is what any implementation must
136+
satisfy. The **`default_ensures`** (`output == input / 2`) is an additional guarantee
137+
that holds *only* when a type uses the default implementation without overriding it.
138+
139+
The rules:
140+
141+
* `default_ensures` is only allowed on a trait function that has a default body in the
142+
trait declaration.
143+
* `default_ensures` is checked against the default body just like a normal `ensures`.
144+
* Callers that statically know the type inherits the default learn both `ensures` and
145+
`default_ensures`; callers using a generic bound `T: Trait` learn only the `ensures`.
146+
147+
```rust
148+
{{#include ../../../../examples/guide/traits.rs:default_ensures_impls}}
149+
```
150+
151+
```rust
152+
{{#include ../../../../examples/guide/traits.rs:default_ensures_callers}}
153+
```
154+
155+
When writing your own trait, consider using `default_ensures` on functions
156+
where a sensible default makes a stronger promise that custom implementations
157+
are not required to match.
158+
159+
## Common `vstd` trait specifications
160+
161+
`vstd` provides specifications for many standard library traits. A few worth knowing:
162+
163+
* **`PartialEq` / `Eq`**`vstd` wraps these with an `obeys_eq_spec()` guard so that
164+
only types that opt in are assumed to satisfy the functional equality contract.
165+
See [External trait specifications](./external_trait_specifications.md) for the
166+
`obeys_*` pattern.
167+
* **`Iterator`**`vstd` provides `IteratorSpecImpl` (not `Iterator` directly) for
168+
writing specs on custom iterators. See [Iterator Specifications](./iterator-specs.md).
169+
* **`PartialOrd` / `Ord`** — similar to `PartialEq`, with `obeys_ord_spec()` guards.
170+
171+
## External trait specifications
172+
173+
For adding specifications to traits from external crates (including `std`), see
174+
[External trait specifications](./external_trait_specifications.md).

0 commit comments

Comments
 (0)