natord
Natural ordering comparison functions for sorting strings the way humans expect, digits and all.
Repository Health
Technical Analysis
Natord provides natural-order string comparison for Rust, meaning strings containing embedded numbers sort the way a human would expect (“file2” before “file10”) instead of plain lexicographic ASCII ordering. It exposes compare for case-sensitive comparison, compare_ignore_case for case-insensitive comparison, and the lower-level compare_iter for building custom natural-order comparators over arbitrary iterators of “characters.”
The algorithm is inspired by Martin Pool’s classic strnatcmp.c, treating runs of decimal digits as numeric segments while comparing everything else character by character, with explicit handling for leading zeros (left-aligned matching) versus differing-length digit runs (right-aligned matching). It is a small, dependency-free crate suited to anywhere a sort_by needs to respect embedded numbers, such as filenames, version-like identifiers, or user-facing lists.
What You Get
compare(a, b)— case-sensitive natural-order comparison returning a standardOrderingcompare_ignore_case(a, b)— case-insensitive variant using Unicode-awareto_lowercasebefore comparingcompare_iter— a generic, iterator-based comparator you customize with your ownskip,cmp, andto_digitclosures- Zero runtime dependencies and a tiny, single-file implementation
Common Use Cases
- Sorting filenames or paths so
file2.txtcomes beforefile10.txt - Ordering version-like or numbered identifiers in UI lists
- Implementing natural sort in CLI tools that list numbered items
- Building custom natural-order comparators for domain-specific token streams via
compare_iter
Under The Hood
Architecture
Natord is a single-file crate (lib.rs) built around one generic core function, compare_iter, which drives the natural-ordering algorithm over any two iterators of “characters” using injected skip, cmp, and to_digit closures; the public compare and compare_ignore_case functions are thin wrappers that supply char-specific closures (Unicode whitespace skipping, char ordering, and decimal-digit parsing) to this shared engine, so the crate’s logic funnels through one control-flow-heavy loop that uses internal macros (read_left!, read_right!, return_unless_equal!) to track left/right cursor state and digit runs. There are no internal modules or layers to reason about, and the one abstraction everything depends on is compare_iter’s digit/skip/cmp closure contract.
Tech Stack
The crate targets stable Rust with zero external dependencies, relying solely on std::cmp::Ordering and iterator/closure generics (FnMut, Iterator<Item=T>). Cargo.toml declares a single library target (crate_type = "lib", path = "lib.rs") under the MIT license with no features or workspace members, and the only CI configuration present is a legacy .travis.yml — there is no modern GitHub Actions workflow, linter configuration, or other build tooling in the repo.
Code Quality
Tests live in an inline #[cfg(test)] mod tests block using a shared check_total_order helper that asserts full pairwise ordering (not just adjacent comparisons) across curated string arrays covering numeric suffixes, multi-part identifiers, leading zeros, and a long mixed real-world-style list. There is no unsafe code, the generic function signature encodes its natural-ordering contract in the type system via the Skip/Cmp/ToDigit closure bounds, and naming is consistent throughout, though there is no linter/formatter configuration and CI is limited to an inactive Travis setup rather than an actively-running pipeline.
API Design
The public surface is minimal and ergonomic — two ready-to-use functions (compare, compare_ignore_case) slot directly into Vec::sort_by with no setup, plus a generic compare_iter escape hatch for non-string use cases. The crate-level doc comment includes a runnable example showing the exact sort_by idiom, so getting started requires no boilerplate beyond a single import. The lower-level generic parameters demand some understanding of the algorithm’s internals, and the crate hasn’t been updated since 2018, so it predates newer idiomatic conveniences such as an Ord-wrapper type or a one-call sort_natural helper.