remain
A Rust attribute macro that enforces alphabetically sorted enum variants, struct fields, and match arms at compile time.
Repository Health
Technical Analysis
remain provides the #[remain::sorted] attribute macro, which checks at compile time that the variants of an enum, the fields of a struct, or the arms of a match expression are written in sorted order. If an item is inserted out of order, the macro emits a compile error pointing at exactly where the item should have gone, keeping large enums, error types, and match statements easy to scan and diff.
Because the check runs during macro expansion rather than as a separate lint pass, it works everywhere the attribute is placed — no build script wiring or CI-only tooling required. On stable Rust, the companion #[remain::check] function-level attribute enables the match/let-expression form of the check, which otherwise needs nightly features.
What You Get
#[remain::sorted]attribute for enums, structs, match expressions, and let-match statements- Compile-time error messages that name the exact out-of-order item and where it should sort
#[remain::check]/#[check]function-level attribute that unlocks match/let checking on stable Rust without nightly features- Per-item
#[remain::unsorted]/#[unsorted]escape hatch to exclude specific variants, fields, or arms (e.g. a trailing__Nonexhaustivecatch-all) from the sort requirement - Locale-independent, purely lexicographic ordering with defined rules for underscores, casing, and numeric suffixes
Common Use Cases
- Keeping large
Errorenums alphabetically ordered so new variants are easy to place and diffs stay small - Enforcing sorted match arms in
Display/Debugimplementations that mirror a sorted error enum - Sorting configuration or options structs with many named fields for faster visual scanning
- Adding a lightweight, zero-runtime-cost style rule to a codebase without a separate linter pass or CI script
Under The Hood
Architecture — remain is a proc-macro = true crate with two exported attribute macros, sorted and check, wired up in src/lib.rs. sorted parses its input into an Input enum (Enum/Struct/Match/Let, defined in src/parse.rs) via a hand-written syn::parse::Parse impl that forks the token stream to peek at match/let/enum/struct keywords before committing. src/check.rs then walks the parsed item’s variants/fields/arms (unified behind a Sortable trait), converts each into a Path of identifier segments (handling multi-segment match patterns like Foo::Bar), strips any item carrying #[unsorted]/#[remain::unsorted], and runs a linear scan for the first out-of-order pair using src/compare.rs’s custom cmp — first trying underscore-prefixed identifiers sorted first, then re-checking with underscores sorted last (matching Rust’s own __Nonexhaustive-style conventions) before reporting a real error. On failure, src/emit.rs splices a syn::Error’s compile_error! output alongside the original tokens so the macro degrades gracefully — the caller still gets the original item plus a precise error, not a total failure. Tech Stack — the crate depends on exactly three libraries at the versions pinned in Cargo.toml: proc-macro2 (1.0.74), quote (1.0.35), and syn (3, with full+visit-mut features) — the standard trio for hand-rolled proc-macros — with rustversion and trybuild as dev-dependencies for compiler-version-gated UI tests. edition = 2021, rust-version = "1.71", and a build.rs that detects nightly-only stmt_expr_attributes support to gate the unstable test suite. Code Quality — the crate is organized into small, single-responsibility modules (atom.rs for identifier-segment comparison atoms, compare.rs for ordering rules, parse.rs/check.rs/emit.rs/visit.rs/format.rs), each under 250 lines. Testing is thorough for a macro crate: tests/stable.rs and tests/unstable.rs cover the attribute + unsorted-escape-hatch behavior on stable vs. nightly compilers respectively, tests/order.rs stress-tests the comparator against underscore placement, snake_case, and numeric-suffix edge cases (E1/E9/E10), and tests/ui/ holds trybuild snapshot tests asserting exact compiler error text. No unsafe code is used. API Design — the public surface is exactly two attributes (#[remain::sorted], #[remain::check]) plus one per-item escape hatch (#[unsorted]), documented with runnable doctests directly in src/lib.rs. Zero configuration or setup beyond adding the crate and the attribute is required, and error messages name the specific offending identifier and its correct position rather than a generic “not sorted” message, making the developer experience for such a narrowly-scoped macro very good.