caseless

Unicode-correct caseless string matching for Rust using default, canonical, and compatibility case folding.

Library
Cargo
v0.2.2
26stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
23/100Needs Attention
Development Activity0
Maintenance0
Community20
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
49/100Fair
Architecture68
Code Quality48
Innovation55
Learning Curve25

caseless is a small Rust crate implementing the Unicode Default Case Algorithms for caseless string comparison. It exposes a Caseless trait usable on any char iterator, adding default_case_fold, default_caseless_match, canonical_caseless_match, and compatibility_caseless_match methods, plus free _str helper functions for direct string comparison.

Under the hood it ships a generated case-folding table sourced from the Unicode Consortium’s official CaseFolding.txt, combined with NFD/NFKD normalization from the unicode-normalization crate to implement canonical and compatibility caseless matching per the Unicode standard. A companion print-table workspace member regenerates the table from the official Unicode data file, so the crate can be kept current as new Unicode versions ship.

What You Get

  • A Caseless trait implemented for any Iterator<Item = char>, adding case-fold and caseless-match methods without wrapping your strings in a new type.
  • Three levels of caseless comparison — default, canonical (NFD-normalized), and compatibility (NFKD-normalized) — matching the tiers defined in Unicode Standard section 3.13.
  • Free-standing _str helper functions (default_case_fold_str, default_caseless_match_str, etc.) for one-line string comparisons without touching the trait.
  • A precomputed case-folding table generated directly from the Unicode Consortium’s official CaseFolding.txt, so folding behavior matches the standard exactly rather than approximating it with ASCII-only logic.

Common Use Cases

  • Comparing user-entered identifiers (usernames, tags, search terms) for equality regardless of case, including non-Latin scripts.
  • Implementing case-insensitive matching in text-processing or parsing pipelines that must handle full Unicode input.
  • Deduplicating or indexing strings where case differences (e.g. ‘Straße’ vs ‘STRASSE’) should be treated as equivalent.

Under The Hood

Architecture The crate is a flat, single-purpose library: a Caseless trait blanket-implemented for any Iterator<Item = char>, one adapter type (CaseFold) that wraps a char iterator with a small fixed-size lookahead queue, and a handful of free _str convenience functions. CaseFold::next drives a binary search into a static, generated CASE_FOLDING_TABLE to emit folded characters (up to three per input character). The three matching tiers (default_caseless_match, canonical_caseless_match, compatibility_caseless_match) layer unicode-normalization’s nfd()/nfkd() iterator adapters on top of case folding before a private iter_eq helper compares the resulting streams lazily, without allocating a String. There is no error handling surface because every operation on well-formed char input is infallible; the one fragile point is that CaseFold’s binary search assumes CASE_FOLDING_TABLE stays sorted by source character, a property enforced only by how the table is generated, not by the type system.

Tech Stack A minimal Rust crate (no edition pinned in Cargo.toml, so it defaults to the 2015 edition) with a single runtime dependency, unicode-normalization = "0.1", used purely for its NFD/NFKD iterator adapters. The repo is a Cargo workspace whose second member, print-table, is a publish = false dev-only binary (depending on regex) that regenerates src/case_folding_data.rs from the Unicode Consortium’s CaseFolding.txt. CI is GitHub Actions running cargo build/cargo test under RUSTFLAGS: -D warnings, plus a dedicated regen job that re-downloads the current CaseFolding.txt and diffs both the source data file and the regenerated table to catch drift from new Unicode releases.

Code Quality Test coverage is thin: a single inline #[cfg(test)] mod tests in lib.rs with one test function covering four case-fold assertions (plain ASCII, the long-s ſ, the ligature, and German ß) — there are no integration tests, property tests, or benchmarks despite the crate’s entire value proposition being correctness on Unicode edge cases. Naming is idiomatic Rust throughout (snake_case functions, PascalCase types), and there are no custom error types since every public function is total over its input, which is an appropriate design choice rather than an omission. The CaseFold iterator manually manages a fixed two-slot lookahead array instead of a VecDeque, trading a small amount of readability for zero-allocation iteration. -D warnings in CI is the only enforced lint gate; there is no clippy step and no code-coverage tooling.

API Design The trait-on-iterator pattern means callers reach .default_case_fold() directly off str::chars() with a single use caseless::Caseless; import and no new wrapper type to learn, and the _str free functions remove even that step for the common case of comparing two &str values. The tradeoff is that choosing between the three near-identical matching tiers (default_caseless_match vs canonical_caseless_match vs compatibility_caseless_match) requires knowing Unicode Standard section 3.13, since the crate’s public items carry no rustdoc explaining when to reach for which — the only guidance is a source comment referencing the spec. The README itself is a single line, so discoverability of the API’s nuances depends entirely on reading lib.rs.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search