# Using assert and assume to develop proofs
In [an earlier chapter](./spec_lib.md), we started with an outline of a proof:
```rust
{{#include ../../../../examples/guide/lib_examples.rs:lemma_len_intersect_fail}}
```
and then filled in the crucial missing steps to complete the proof.
It didn't say, though,
how you might go about discovering which crucial steps are missing.
In practice, it takes some experimentation to fill in this kind of proof.
This section will walk through a typical process of developing a proof,
using the proof outline above as a starting point.
The process will consist of a series of queries to Verus and the SMT solver,
using `assert` and `assume` to ask questions,
and using the answers to narrow in on the cause of the verification failure.
If we run the proof above, Verus reports an error:
```
error: postcondition not satisfied
|
| s1.intersect(s2).len() <= s1.len(),
| ---------------------------------- failed this postcondition
```
This raises a couple questions:
- Why is this postcondition failing?
- If this postcondition succeeded, would the verification of the whole function succeed?
Let's check the second question first. We can simply assume the postcondition and see what happens:
```rust
pub proof fn lemma_len_intersect(s1: Set, s2: Set)
...
{
if s1.is_empty() {
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
}
assume(s1.intersect(s2).len() <= s1.len());
}
```
In this case, verification succeeds:
```
verification results:: verified: 1 errors: 0
```
There are two paths through the code, one when `s1.is_empty()` and one when `!s1.is_empty()`.
The failure could lie along either path, or both.
Let's prepare to work on each branch of the `if`/`else` separately
by moving a separate copy of the `assume` into each branch:
```rust
{
if s1.is_empty() {
assume(s1.intersect(s2).len() <= s1.len());
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assume(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
Next, let's change the first `assume` to an `assert` to see if it succeeds in the `if` branch:
```rust
{
if s1.is_empty() {
assert(s1.intersect(s2).len() <= s1.len());
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assume(s1.intersect(s2).len() <= s1.len());
}
}
```
```
error: assertion failed
|
| assert(s1.intersect(s2).len() <= s1.len());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assertion failed
```
In the `s1.is_empty()` case, we expect `s1.len() == 0` (an empty set has cardinality 0).
We can double-check this with a quick assertion:
```rust
{
if s1.is_empty() {
assert(s1.len() == 0);
assume(s1.intersect(s2).len() <= s1.len());
} else {
...
}
}
```
```
verification results:: verified: 1 errors: 0
```
So what we need is `s1.intersect(s2).len() <= 0`.
If this were true, we'd satisfy the postcondition here:
```rust
{
if s1.is_empty() {
assume(s1.intersect(s2).len() <= 0);
assert(s1.intersect(s2).len() <= s1.len());
} else {
...
}
}
```
```
verification results:: verified: 1 errors: 0
```
Since set cardinality is a `nat`, the only way it can be `<= 0` is if it's equal to `0`:
```rust
{
if s1.is_empty() {
assume(s1.intersect(s2).len() == 0);
assert(s1.intersect(s2).len() <= s1.len());
} else {
...
}
}
```
```
verification results:: verified: 1 errors: 0
```
and the only way it can be `0` is if the set is the empty set:
```rust
{
if s1.is_empty() {
assume(s1.intersect(s2) == Set::::empty());
assert(s1.intersect(s2).len() == 0);
assert(s1.intersect(s2).len() <= s1.len());
} else {
...
}
}
```
```
verification results:: verified: 1 errors: 0
```
So we've narrowed in on the problem:
the intersection of the empty set `s1` with another set should equal the empty set,
but the verifier doesn't see this automatically.
And from the previous section's [discussion of equality](spec_lib.md#proving-properties-of-seq-set-iset-map-and-imap), we can guess why:
the SMT solver doesn't always automatically prove equalities between collections,
but instead requires us to assert the equality using extensionality.
So we can add the extensionality assertion:
```rust
{
if s1.is_empty() {
assert(s1.intersect(s2) =~= Set::::empty());
assert(s1.intersect(s2).len() == 0);
assert(s1.intersect(s2).len() <= s1.len());
} else {
...
}
}
```
```
verification results:: verified: 1 errors: 0
```
It works! We've now verified the `s1.is_empty()` case,
and we can turn our attention to the `!s1.is_empty()` case:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assume(s1.intersect(s2).len() <= s1.len());
}
}
```
Changing this `assume` to an `assert` fails,
so we've got work to do in this case as well:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.intersect(s2).len() <= s1.len());
}
}
```
```
error: assertion failed
|
| assert(s1.intersect(s2).len() <= s1.len());
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assertion failed
```
Fortunately, the recursive call `lemma_len_intersect::(s1.remove(a), s2)` succeeded,
so we have some information from the postcondition of this call.
Let's write this out explicitly so we can examine it more closely,
substituting `s1.remove(a)` for `s1`:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assume(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
Let's compare what we know about `s1.remove(a)` with what we're trying to prove about `s1`:
```rust
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len()); // WE KNOW THIS
assume(s1 .intersect(s2).len() <= s1 .len()); // WE WANT THIS
```
Is there any way we can make what we know look more like what we want?
For example, how does `s1.remove(a).len()` relate to `s1.len()`?
The value `a` is an element of `s1`, so if we remove it from `s1`,
it should decrease the cardinality by 1:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assert(s1.remove(a).len() == s1.len() - 1);
assume(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
So we can simplify a bit:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assert(s1.remove(a).intersect(s2).len() <= s1.len() - 1);
assert(s1.remove(a).intersect(s2).len() + 1 <= s1.len());
assume(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
Now the missing piece is the relation between `s1.remove(a).intersect(s2).len() + 1`
and `s1.intersect(s2).len()`:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assert(s1.remove(a).intersect(s2).len() <= s1.len() - 1);
assert(s1.remove(a).intersect(s2).len() + 1 <= s1.len());
assume(s1.intersect(s2).len() <= s1.remove(a).intersect(s2).len() + 1);
assert(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
If we can prove the assumption `s1.intersect(s2).len() <= s1.remove(a).intersect(s2).len() + 1`,
we'll be done:
```rust
assume(s1 .intersect(s2).len()
<= s1.remove(a).intersect(s2).len() + 1);
```
Is there any way we can make `s1.remove(a).intersect(s2)` look more like `s1.intersect(s2)`
so that it's easier to prove this inequality?
If we switched the order from `s1.remove(a).intersect(s2)` to `s1.intersect(s2).remove(a)`,
then the subexpression `s1.intersect(s2)` would match:
```rust
assume(s1.intersect(s2) .len()
<= s1.intersect(s2).remove(a).len() + 1);
```
so let's try that:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assert(s1.remove(a).intersect(s2).len() <= s1.len() - 1);
assert(s1.remove(a).intersect(s2).len() + 1 <= s1.len());
assert(s1.intersect(s2).len() <= s1.intersect(s2).remove(a).len() + 1);
assert(s1.intersect(s2).len() <= s1.remove(a).intersect(s2).len() + 1);
assert(s1.intersect(s2).len() <= s1.len());
}
}
```
```
error: assertion failed
|
| assert(s1.intersect(s2).len() <= s1.remove(a).intersect(s2).len() + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assertion failed
```
One of these assertions succeeds and the other fails.
The only difference between the successful assertion
and the failing assertion is the order of `intersect` and `remove`
in `s1.intersect(s2).remove(a)` and `s1.remove(a).intersect(s2)`,
so all we need to finish the proof is for `s1.intersect(s2).remove(a)`
to be equal to `s1.remove(a).intersect(s2)`:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assert(s1.remove(a).intersect(s2).len() <= s1.len() - 1);
assert(s1.remove(a).intersect(s2).len() + 1 <= s1.len());
assert(s1.intersect(s2).len() <= s1.intersect(s2).remove(a).len() + 1);
assume(s1.intersect(s2).remove(a) == s1.remove(a).intersect(s2));
assert(s1.intersect(s2).len() <= s1.remove(a).intersect(s2).len() + 1);
assert(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
Again, we found ourselves needing to know the equality of two collections.
And again, the first thing to try is to assert extensional equality:
```rust
{
if s1.is_empty() {
...
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assert(s1.remove(a).intersect(s2).len() <= s1.len() - 1);
assert(s1.remove(a).intersect(s2).len() + 1 <= s1.len());
assert(s1.intersect(s2).len() <= s1.intersect(s2).remove(a).len() + 1);
assert(s1.intersect(s2).remove(a) =~= s1.remove(a).intersect(s2));
assert(s1.intersect(s2).len() <= s1.remove(a).intersect(s2).len() + 1);
assert(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
It works!
Now we've eliminated all the `assume`s, so we've completed the verification:
```rust
pub proof fn lemma_len_intersect(s1: Set, s2: Set)
requires
s1.finite(),
ensures
s1.intersect(s2).len() <= s1.len(),
decreases
s1.len(),
{
if s1.is_empty() {
assert(s1.intersect(s2) =~= Set::empty());
assert(s1.intersect(s2).len() == 0);
assert(s1.intersect(s2).len() <= s1.len());
} else {
let a = s1.choose();
lemma_len_intersect::(s1.remove(a), s2);
assert(s1.remove(a).intersect(s2).len() <= s1.remove(a).len());
assert(s1.remove(a).intersect(s2).len() <= s1.len() - 1);
assert(s1.remove(a).intersect(s2).len() + 1 <= s1.len());
assert(s1.intersect(s2).len() <= s1.intersect(s2).remove(a).len() + 1);
assert(s1.intersect(s2).remove(a) =~= s1.remove(a).intersect(s2));
assert(s1.intersect(s2).len() <= s1.remove(a).intersect(s2).len() + 1);
assert(s1.intersect(s2).len() <= s1.len());
}
}
```
```
verification results:: verified: 1 errors: 0
```
The code above contains a lot of unnecessary `assert`s, though,
so it's worth spending a few minutes cleaning the code up
for the sake of anyone who has to maintain the code in the future.
We want to clear out unnecessary code so there's less code to maintain,
but keep enough information so
someone maintaining the code can still understand the code.
The right amount of information is a matter of taste,
but we can try to strike a reasonable balance between conciseness and informativeness:
```rust
{{#include ../../../../examples/guide/lib_examples.rs:lemma_len_intersect_commented}}
```