typenum
Type-level numbers for Rust, evaluated entirely at compile time.
Repository Health
Technical Analysis
Typenum is a Rust crate that encodes numbers — bits, unsigned integers, and signed integers — as distinct types rather than runtime values, letting the compiler perform arithmetic and comparisons on them during type checking. It underpins libraries that need to parameterize behavior by a fixed, compile-time-known quantity, most notably fixed-size arrays and dimensional/unit-checking systems where the number itself is part of the type signature.
Because typenum depends only on libcore, it works in no_std environments including embedded targets. Its type operators mirror Rust’s standard arithmetic and comparison traits (Add, Sub, Cmp, and friends), so type-level expressions like Sum<P3, P4> read like ordinary arithmetic while being fully resolved before a single byte of the program runs, which turns a class of runtime bugs into compile errors instead.
What You Get
- Compile-time bit, unsigned-integer, and signed-integer types with a full set of arithmetic, comparison, and bitwise type operators (Add, Sub, Mul, Div, Cmp, And, Or, Xor, Shl, Shr, and more)
- Pre-generated constant type aliases (U0..U1024, P1..N1024, and similar) so common values don’t need to be built up recursively by hand
- Optional interop with Rust’s native const generics via the
const-genericsfeature, and 128-bit integer constants via thei128feature - A
no_std-only dependency footprint (libcore only by default), making it usable in embedded and other constrained targets - Marker traits (
NonZero,Zero,Unsigned,Integer,Bit) that let generic code convert a type-level number to its runtime value for debugging or interop - Type-level heterogeneous arrays and tuples (
TArr,ATerm) for composing multiple type-level numbers together
Common Use Cases
- Encoding fixed array/buffer lengths in a type so mismatched sizes are caught at compile time, as used by crates like generic-array
- Building compile-time dimensional-analysis or unit-checking systems where physical units are tracked as type-level exponents, as used by crates like dimensioned
- Parameterizing generic numeric code (e.g. fixed-point or SIMD-width types) by a type-level constant instead of a runtime value or const generic
- Implementing type-level state machines or protocols where a bounded integer must be tracked and validated purely through the type system
- Providing const-generic-compatible numeric types for libraries that need to support both older and newer Rust generic-parameter styles via the
const-genericsfeature
Under The Hood
Architecture
Typenum’s design centers on a binary type-level encoding: UInt<U, B> recursively pairs a higher-order unsigned integer with a single Bit (B0/B1), giving logarithmic-depth type nesting instead of unary counting, while PInt/NInt in int.rs wrap that unsigned representation with a sign marker to cover signed integers. Type operators — Add, Sub, Cmp, and the rest — are defined as traits in type_operators.rs and implemented per-representation across uint.rs and int.rs, so arithmetic resolves through ordinary Rust trait dispatch entirely at compile time. The large gen/ module (consts.rs, generic_const_mappings.rs, op.rs) is machine-generated by a companion generate workspace crate rather than hand-written, supplying pre-built constant aliases and const-generic interop; array.rs and tuple.rs extend the same encoding to heterogeneous compile-time collections (TArr, ATerm). Because every operator implementation pattern-matches on the UInt/Bit shape, changing that core representation would ripple through uint.rs, int.rs, and the entire generated gen/ tree.
Tech Stack
Typenum is a pure, #![no_std] Rust crate (edition 2018, MSRV 1.41.0) depending only on libcore by default, which keeps it usable on embedded and other constrained targets. Its one optional external dependency is scale-info, gated behind the scale_info feature; const-generics and i128 are additional opt-in features with no extra dependencies. The internal generate crate (a Cargo workspace member) programmatically emits the thousands of trait impls in gen/ and the 21,000+ line generated test suite rather than requiring them to be maintained by hand. CI (GitHub Actions) runs the matrix across stable/beta/nightly Rust and cross-compiled targets including i686 and sparc64, and a Nix flake plus justfile support reproducible local development.
Code Quality
Correctness is validated almost entirely through the generated tests/generated.rs suite, which exercises every implemented type-operator combination against expected runtime values, supplemented by doctests embedded in marker_traits.rs and type_operators.rs. The crate enforces #![forbid(unsafe_code)] crate-wide — no unsafe blocks are permitted anywhere — and #![deny(missing_docs)] under its “strict” feature, with clippy configured via clippy.toml. Naming is consistent and predictable (UInt/UTerm, PInt/NInt, B0/B1, U0..Un), and because the crate has no runtime logic, error handling is effectively delegated to the Rust compiler’s trait-resolution failures rather than any hand-written error path.
API Design
Typenum re-exports its entire public surface through the crate root, so consumers can use typenum::* or treat the consts module as a glob-importable prelude without navigating internal module boundaries — documented explicitly in the README’s “Importing” section. Helper type aliases (Sum, Exp, and similar) hide the raw associated-type bounds that direct trait usage would otherwise require, so type-level expressions read close to ordinary arithmetic. The most honest tradeoff, acknowledged directly in the README, is that failed type-level computations surface as dense, hard-to-parse compiler trait-resolution errors, enough of a pain point that the community built an external tool (tnfilt) specifically to help decode them.