Status: Accepted Date: September 23, 2026 Related: ADR-203 D2 (the unit-only case, which this subsumes), ADR-005 §1 Group B and ADR-123 D1 (the structural walk and the hand-written column this is modelled on), ADR-010 D1 (absence is not permission), ADR-288 (the emitter has no types, so the checker answers)
Without a decision, a == b over a declared type has no lowering:
error: binary operation `==` cannot be applied to type `P`
= an implementation of `PartialEq` might be missing for `P`
= consider annotating `P` with `#[derive(PartialEq)]`
on the author’s line, in the backend’s words about a file nobody wrote, with a help the source cannot take — Part III C.1 and C.2 at once.
A blanket derive is the wrong answer: it would refuse the declaration of
every type holding a Locked[T], a Mapped or a socket, which is a worse place
to be wrong than the comparison
(#157).
The walk is the one ADR-005 §1 Group B already asks for may this cross a thread, pointed at a different question:
bool, char,
String, Bytes, and a container exactly when what it holds does;struct answers from its fields and a declared enum from its
variants’ payloads, recursively, and a type that holds itself answers once
rather than forever;compares column.Absence is not permission (ADR-010 D1). A std type without
the line does not compare: a lock, a mapping, a socket, a task’s handle, a
channel’s ends. compares = true is a claim reviewed like code, which is exactly
what crosses = true is and for the same reason — nothing in the compiler can
walk a Rust type’s parts.
A type this compiler could not work out compares, which is the answer an absent claim gets everywhere (Part III C.4): a refusal on a guess is a correct program refused, and it is the worse of the two mistakes.
Where one rule ends and the other begins is whether anything knows the name (#379):
std’s, or a described crate’s (ADR-290). A known type without
compares = true does not compare. A described crate’s type is a std type in
this: its description is reviewed like code (ADR-290 D5), so a missing line
there is a reviewer’s no, not a nobody said. std’s own types that have no
entry, such as Locked, are known by being std’s.NK1135’s, NK2504’s or rustc’s refusal, and NK1188 beside it
would be a second refusal naming the wrong cause.nikaia describe reads an impl PartialEq for T as it reads the derive, so
the line a hand-written comparison earns is drafted rather than left for the
reviewer to notice. Only a trait written without an argument counts -
PartialEq<str> compares the type with something else - and only for a type in
the same module as the impl; one elsewhere is the reviewer’s line to add.
Eq is the same walk with one primitive answering differently.Rust’s f64 is PartialEq and not Eq, because NaN != NaN. So a float
compares and is not an equivalence, and every type holding one differs with it,
however far down. It is the same recursion with one arm changed rather than a
look at the type’s syntax: Json::Number(f64) makes Json PartialEq and not
Eq, so a struct holding a Json is not Eq either.
A library type is never taken for an equivalence. Whether one is, is a second
claim nobody has had a use for, so a struct holding a time::Duration compares
and is not a map key yet. That is the under-approximation that cannot be wrong,
and the day something needs it the column gains a second value rather than a
guess.
== on a type that does not compare is NK1188, here.Changed by ADR-341 D5: a program may write impl Eq for a type it declares, which replaces the equality the walk would have derived.
The refusal names the type and says what decides it, and its help is one a
program can take: compare the parts that carry the answer, or give the type a
method that says what equality means for it. There is no #[derive] to write,
which is what makes the backend’s help a way out that cannot be taken.
Asked of one side and only where its type is known. The two sides disagreeing
is NK1102’s business one refusal over, and a type this compiler could not work
out is left alone.
Named variant’s fields, so Number(f64) is not taken for a variant holding
nothing. That map is what D2 reads.enum has
no parts, so the walk answers yes for it by the same sentence rather than by
a special case.< on a declared type would be PartialOrd, and what an order
over an enum’s cases or a struct’s fields means is a question nobody has
asked. A program that wants one writes a method.Hash needs Eq and
agreement between the two, and a map keyed by a program’s own type is a thing
to want before it is a thing to build.
Decided by ADR-010 D11: a declared type hashes from its fields where every field does.time::Duration is an equivalence is the second value the column would gain,
and D2 says why it has not.== on a trait object, which does not exist: a trait is never the type of
a value here (ADR-295).