We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Shape Your Data: Structs, Enums, Pattern Matching step 20 of 26
PartialOrd and Ord: derived ordering follows declaration order
Two functions, both graded.
pub fn sort_versions(versions: Vec<String>) -> Vec<String>
pub fn rank_priorities(items: Vec<(String, String)>) -> Vec<String>
sort_versions sorts semver-ish strings correctly:
["1.10.0", "1.9.0"] becomes ["1.9.0", "1.10.0"]. Missing or unparseable
components count as 0, so "2", "2.0" and "2.0.0" all mean the same
version — order equal versions by the original string so the result is
deterministic.
rank_priorities takes (name, priority_label) pairs and returns the
names ordered most urgent first: critical, high, normal, then anything
else as low. Ties break by name.
You should not write a single comparison function. Both orderings come for
free from #[derive(Ord)].
Derived ordering is lexicographic by declaration order
For a struct, the derived Ord compares the first field first; only if
that ties does it look at the second, and so on:
#[derive(PartialEq, Eq, PartialOrd, Ord)]
struct Version { major: u32, minor: u32, patch: u32 }
That is semver, exactly, with no code. And it defeats the classic bug: string
sorting puts "1.10.0" before "1.9.0" because '1' < '9'. Comparing
(1, 10, 0) against (1, 9, 0) as numbers does the right thing.
For an enum, the derived Ord compares variant declaration order:
earlier variants are less than later ones. So declaring
enum Priority { Critical, High, Normal, Low }
makes Critical < High < Normal < Low, and sorting ascending gives you the
triage list. Payloads are compared only within the same variant.
Tuples derive the same way, which is why Vec<(Priority, String)>::sort()
ranks by priority and then breaks ties by name with no work from you.
The landmine
Reordering fields or variants changes your program’s ordering, and nothing
warns you. Move patch above major because it reads better, and every
comparison in your codebase silently changes meaning. No compiler error, no
clippy lint, no test failure unless you happened to have one.
This is the clearest small example of the principle from the derive item: derive is generated code, and you own what it generates. If the ordering matters, say so in a comment next to the declaration, or write the impl by hand.
The deny-by-default lint
derive_ord_xor_partial_ord is a correctness lint denied by default. It
fires when you derive one of the pair and hand-write the other, in either
direction:
error: you are deriving `Ord` but have implemented `PartialOrd` explicitly
The reason is that a.partial_cmp(b) must agree with Some(a.cmp(b)) — the
standard library, sort, BTreeMap and everything else assumes it. Two
independently written implementations will eventually disagree. The starter
ships this exact mistake, with a partial_cmp that only compares major, so
you can see both the lint and the wrong answers it predicts.
Its companion, non_canonical_partial_ord_impl, says the same thing more
concretely: if a type is Ord, its partial_cmp should be exactly
Some(self.cmp(other)) and nothing else.
Deriving Ord also requires Eq and PartialOrd. #[derive(PartialEq, Ord)]
on its own gives you E0277: the trait bound Version: Eq is not satisfied.
An honest note about broken comparators
Since Rust 1.81 the sort implementations may detect that a comparator
does not define a total order and panic with “user-provided comparison
function does not correctly implement a total order”. The docs say may,
and they mean it. Verified on 1.95: a comparator that always returns Less
does not panic at n = 20, 40, 100, 1 000 or 10 000 — it returns quietly
with garbage order — while a pseudo-random comparator does panic from
around n = 40 upward. Detection is best-effort.
The docs also warn that “even if the function exits normally, the resulting
order of elements in the slice is unspecified.” So: never write a test that
depends on the panic firing, and never assume a quiet sort means a correct
comparator. When the panic does fire it is an ordinary unwinding panic, which
catch_unwind will catch — it is not an abort.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.