ADR-341: Eq and Ord Are Traits of the Language, a Bound on Them Gives the Operators, and a Program Writes impl Eq and impl Ord for the Types It Declares

Status: Accepted Date: October 12, 2026 (#592) Answers: whether a generic function can ask that its type be comparable ([T: Ord]), what the bound gives the body, and who answers it Related: ADR-204 (a type compares when its parts do; == on one that does not is NK1188), ADR-010 D10 (a map kept in order asks its key for an order), ADR-295 D7-D8 (a bound is written [T: Trait] and answered from the ledger), Part I 4.7

1. Question

Can a generic function say that its type is comparable, and what does the bound give the body?

fn biggest[T: Ord](a: T, b: T) -> T {
    if a > b { return a }
    return b
}

Today this is refused with NK1135 (there’s no trait called Ord). Without the bound, a > b on a T passes the checker and is refused by rustc (binary operation > cannot be applied to type T), which is Part III C.1: an error about generated code reaches the user. Libraries that want to accept “anything sortable” or “any key that can be ordered” have nothing to write.

2. How others do it

Language Its answer Source
Rust traits Eq, PartialOrd, Ord with cmp -> Ordering; #[derive] or a hand-written impl; a bound gives the operators; the orphan rule limits an impl to a local type or a local trait from memory, not checked
Swift protocols Equatable, Comparable; a type conforms by writing == and < from memory, not checked
Haskell classes Eq, Ord with an instance per type; compare -> Ordering from memory, not checked
Go built-in constraints comparable and cmp.Ordered; no user conformance, a type is in or out by its kind from memory, not checked
Kotlin, Java Comparable<T>.compareTo; the operators are the method by convention (Kotlin) or absent (Java) from memory, not checked

3. Options, weighed

Question Option Why it lost / won
who answers a bound the language alone, from its own walk (Go) lost: a program could never say what order its own type has, and a library could not offer one
  the language for its own types, and the program through impl won: the owner’s decision; the language’s walk stays the answer where nobody wrote an impl
what the bound gives the method only (a.compare(b)) lost: the bound exists to make a > b legal in the body
  the operators won: the owner’s decision; it is what a reader expects of [T: Ord]
the order’s method less(self, other) -> bool lost: an ordered map needs a three-way answer, and less asked twice costs two calls per step
  compare(self, other) -> Ordering won: one call per step, as every language above that has a three-way answer
an order for a declared type that nobody wrote field order, derived lost: it makes the order of the fields a promise nobody asked for (ADR-010 D10)
  none until impl Ord won
the set of standard bounds Eq, Ord, Hash, Copy, Display, Iterator lost: the last three carry methods and semantics of their own
  Eq and Ord won: the owner’s decision; Hash follows with the hash-map key check (#591)

4. Decision

D1 — Eq and Ord are traits of the language, in scope everywhere

trait Eq  { fn eq(ref self, other: ref Self) -> bool sync }
trait Ord { fn compare(ref self, other: ref Self) -> Ordering sync }
enum Ordering { Less, Equal, Greater }

They are Rust’s own, not look-alikes. A trait method without sync lowers to an async fn, which no Rust generic function over T: Ord can call, so both are sync and lower to the standard library’s: Eq to PartialEq and Eq (eq), Ord to PartialOrd and Ord (compare is cmp), Ordering to std::cmp::Ordering. A bound [T: Ord] is Rust’s T: Ord. A Nikaia type with an impl Ord is therefore an Ord type to Rust code (a BTreeMap key, a sort), and a Rust type whose ledger says orders = true satisfies the bound. PartialEq and PartialOrd are not named: a float is compared with == and < where its type is known, and has neither trait here.

Self is the implementing type. A program that declares a trait or an enum of one of these names keeps its own, as it does for any word the language uses (ADR-298). Nothing is imported: they are named the way String is.

D2 — A bound gives the body the operators of its trait

[T: Eq] gives == and != on two T. [T: Ord] gives <, <=, > and >=, the method compare, and also == and !=: an ordered type is an Eq one, as in Rust. A float is neither (NaN), so a T: Ord is never an f64; < on a concrete f64 stays what it was.

D3 — An operator on a type parameter that no bound allows is NK1248

a < b on a T with no Ord bound is refused where it is written, naming the bound that would allow it (< needs T: Ord). It is the same refusal for == and Eq.

D4 — A bound is answered by an impl, and where there is none by the language, from the type’s parts

D5 — impl Eq and impl Ord are written for a type the program declares

An impl Eq for P replaces the equality the walk would have derived for P; an impl Ord for P gives P an order. For a type another package declares the ledger answers, and an impl of these two traits for it is refused (NK1249, the orphan rule of the language below). impl Ord for P is refused (NK1249) where P is not Eq, by the walk or by an impl Eq.

A body that can pause is refused (NK1129): the bridge is not asynchronous.

The laws are the program’s: equality is reflexive, symmetric and transitive; the order is total and compare answers Equal exactly where eq answers true. The compiler does not check them. A type that breaks them gives an ordered map no meaning, and the language says so rather than refusing what it cannot see.

D6 — A map kept in order asks its key for Ord

NK1247 (ADR-010 D10) is the question is the key Ord, answered by D4. A declared type is no longer refused as a key where the program wrote impl Ord for it.

5. Consequences

6. What this does not decide

This site is open source. Improve this page.