Equality via extensionality
In the specification libraries section,
we introduced the extensional equality operator =~=
to check equivalence for Seq
, Set
, and Map
.
Suppose that a struct
or enum
datatype has a field containing Seq
, Set
, and Map
,
and suppose that we’d like to prove that two values of the datatype are equal.
We could do this by using =~=
on each field individually:
struct Foo {
a: Seq<int>,
b: Set<int>,
}
proof fn ext_equal_struct() {
let f1 = Foo { a: seq![1, 2, 3], b: set!{4, 5, 6} };
let f2 = Foo { a: seq![1, 2].push(3), b: set!{5, 6}.insert(4) };
// assert(f1 == f2); // FAILS -- need to use =~= first
assert(f1.a =~= f2.a); // succeeds
assert(f1.b =~= f2.b); // succeeds
assert(f1 == f2); // succeeds, now that we've used =~= on .a and .b
}
However, it’s rather painful to use =~=
on each field every time to check for equivalence.
To help with this, Verus supports the #[verifier::ext_equal]
attribute
to mark datatypes that need extensionality on Seq
, Set
, Map
, Multiset
, spec_fn
fields or fields of other #[verifier::ext_equal]
datatypes. For example:
#[verifier::ext_equal] // necessary for invoking =~= on the struct
struct Foo {
a: Seq<int>,
b: Set<int>,
}
proof fn ext_equal_struct() {
let f1 = Foo { a: seq![1, 2, 3], b: set!{4, 5, 6} };
let f2 = Foo { a: seq![1, 2].push(3), b: set!{5, 6}.insert(4) };
assert(f1.a =~= f2.a); // succeeds
// assert(f1 == f2); // FAILS
assert(f1 =~= f2); // succeeds
}
(Note: adding #[verifier::ext_equal]
does not change the meaning of ==
;
it just makes it more convenient to use =~=
to prove ==
on datatypes.)
Collection datatypes like sequences and sets can contain other collection datatypes as elements
(for example, a sequence of sequences, or set of sequences).
The =~=
operator only applies extensionality to the top-level collection,
not to the nested elements of the collection.
To also apply extensionality to the elements,
Verus provides a “deep” extensional equality operator =~~=
that handles arbitrary nesting of collections, spec_fn
, and datatypes.
For example:
proof fn ext_equal_nested() {
let inner: Set<int> = set!{1, 2, 3};
let s1: Seq<Set<int>> = seq![inner];
let s2 = s1.update(0, s1[0].insert(1));
let s3 = s1.update(0, s1[0].insert(2).insert(3));
// assert(s2 == s3); // FAILS
// assert(s2 =~= s3); // FAILS
assert(s2 =~~= s3); // succeeds
let s4: Seq<Seq<Set<int>>> = seq![s1];
let s5: Seq<Seq<Set<int>>> = seq![s2];
assert(s4 =~~= s5); // succeeds
}
The same applies to spec_fn
, as in:
#[verifier::ext_equal] // necessary for invoking =~= on the struct
struct Bar {
a: spec_fn(int) -> int,
}
proof fn ext_equal_fnspec(n: int) {
// basic case
let f1 = (|i: int| i + 1);
let f2 = (|i: int| 1 + i);
// assert(f1 == f2); // FAILS
assert(f1 =~= f2); // succeeds
// struct case
let b1 = Bar { a: |i: int| if i == 1 { i } else { 1 } };
let b2 = Bar { a: |i: int| 1int };
// assert(b1 == b2); // FAILS
assert(b1 =~= b2); // succeeds
// nested case
let i1 = (|i: int| i + 2);
let i2 = (|i: int| 2 + i);
let n1: Seq<spec_fn(int) -> int> = seq![i1];
let n2: Seq<spec_fn(int) -> int> = seq![i2];
// assert(n1 =~= n2); // FAILS
assert(n1 =~~= n2); // succeeds
}