Skip to content

← Shape Your Data: Structs, Enums, Pattern Matching step 20 of 26

Medium Primitives

Derive consistent ordering for versions and priorities

Fix the starter's inconsistent comparison traits so both sorting functions follow the rules below. Use derived PartialEq, Eq, PartialOrd and Ord for the supplied types, without a custom comparison function.

pub fn sort_versions(versions: Vec<String>) -> Vec<String>
pub fn rank_priorities(items: Vec<(String, String)>) -> Vec<String>

Sort version strings by three numeric components

Split each string on . and use only the first three parts as major, minor and patch. Parse each part as u32; a missing or invalid part becomes zero. Ignore additional parts. Do not trim whitespace.

Rust's integer parser accepts decimal digits with an optional leading + and leading zeros, provided the number fits in u32. Empty parts, negative values, whitespace, underscores and values above 4294967295 fail to parse.

Compare (major, minor, patch) numerically in that order. When these three values tie, compare the original strings using Rust's lexicographic String order. Return the original strings, preserving all duplicates.

Examples:

  • ["1.10.0", "1.9.0"] becomes ["1.9.0", "1.10.0"].
  • ["2.0", "2.0.0", "2"] becomes ["2", "2.0", "2.0.0"].
  • "1..3.extra" parses as (1, 0, 3).
  • "1.2.3-beta" parses as (1, 2, 0).

This is a simplified exercise parser, not a SemVer implementation. It does not interpret prerelease identifiers or build metadata.

Rank priorities, then names

Each pair is (name, priority_label). Match labels exactly: critical comes first, followed by high, then normal. Every other label means low priority, including low, High, and strings with extra whitespace.

Return the names sorted by that priority and then by name. Names and version strings may contain arbitrary Unicode. String comparison is lexicographic, not locale-aware or case-insensitive. Names are preserved unchanged, and duplicate items still produce duplicate names. Each function returns an empty vector for empty input; the harness tests both functions in each case.

Struct fields are compared in declaration order

A derived struct comparison checks the first field, then the next only if the preceding fields tie. This complete example sorts a two-part version:

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Release {
    major: u32,
    minor: u32,
}

fn main() {
    let mut releases = [
        Release { major: 1, minor: 10 },
        Release { major: 2, minor: 0 },
        Release { major: 1, minor: 9 },
    ];
    releases.sort();
    assert_eq!(releases, [
        Release { major: 1, minor: 9 },
        Release { major: 1, minor: 10 },
        Release { major: 2, minor: 0 },
    ]);
}

The supplied Version adds a third field, patch. A tuple such as (Version, String) already implements lexicographic ordering when its elements do, so the second element supplies the original-string tie-break. No handwritten comparator is needed.

Enum order depends on discriminants

The supplied Priority variants are Critical, High, Normal, Low, with no explicit discriminant values. Their derived order follows that declaration order. Sorting (Priority, String) ascending therefore ranks urgency first and names second.

More generally, derived enum ordering compares discriminants before payload fields. Explicit discriminant values can change the order: a variant declared first with value 2 sorts after one declared second with value 1. Within the same variant, payload fields are compared lexicographically.

Field order and enum discriminants are therefore part of the comparison behavior. Keep a test that expresses the intended order when reorganizing declarations. See the Ord documentation.

Keep PartialOrd and Ord consistent

The starter derives Ord for all Version fields but implements PartialOrd using only major. For 1.9.0 and 1.10.0, those implementations disagree: cmp returns Less, while partial_cmp returns Some(Equal).

An Ord type must satisfy a.partial_cmp(b) == Some(a.cmp(b)), along with the ordering and equality laws. Clippy's derive_ord_xor_partial_ord flags the mixed implementations. Remove the manual implementation and derive all four comparison traits together. Merely silencing the lint leaves the inconsistency.

Handwritten implementations are not inherently inconsistent; they must agree on the same order. For an Ord type, delegating partial_cmp to Some(self.cmp(other)) is the canonical approach. This exercise uses derives because declaration order already expresses the desired comparisons.

A sort may panic when its comparison rules are inconsistent, but it is not a validator for those rules. Successful completion does not prove correctness, and invalid ordering can leave the result unspecified. Test comparisons and expected outputs directly rather than depending on a panic. Whether a panic unwinds or aborts depends on the build's panic strategy.