Equality
Equality behaves differently in ghost code than in executable code.
In executable code, Rust defines ==
to mean a call to the eq
function of the PartialEq
trait:
fn equal1(x: u8, y: u8) {
let eq1 = x == y; // means x.eq(y) in Rust
let eq2 = y == x; // means y.eq(x) in Rust
assert(eq1 ==> eq2); // succeeds
}
For built-in integer types like u8
, the x.eq(y)
function is defined as we’d expect,
returning true
if x
and y
hold the same integers.
For user-defined types, though, eq
could have other behaviors:
it might have side effects, behave nondeterministically,
or fail to fulfill its promise to implement an
equivalence relation,
even if the type implements the Rust Eq
trait:
fn equal2<A: Eq>(x: A, y: A) {
let eq1 = x == y; // means x.eq(y) in Rust
let eq2 = y == x; // means y.eq(x) in Rust
assert(eq1 ==> eq2); // won't work; we can't be sure that A is an equivalence relation
}
In ghost code, by contrast, the ==
operator is always an equivalence relation
(i.e. it is reflexive, symmetric, and transitive):
fn equal3(x: u8, y: u8) {
assert({
let eq1 = x == y;
let eq2 = y == x;
eq1 ==> eq2
});
}
Verus defines ==
in ghost code to be true when:
- for two integers or booleans, the values are equal
- for two structs or enums, the types are the same and the fields are equal
- for two
&
references, two Box values, two Rc values, or two Arc values, the pointed-to values are the same - for two RefCell values or two Cell values, the pointers to the interior data are equal (not the interior contents)
In addition, collection dataypes such as Seq<T>
, Set<T>
, and Map<Key, Value>
have their own definitions of ==
,
where two sequences, two sets, or two maps are equal if their elements are equal.
As explained more in specification libraries and extensional equality,
these sometimes require the “extensional equality” operator =~=
to help prove equality
between two sequences, two sets, or two maps.