radix
Zero-dependency Rust crate for converting numbers between radices 2 through 36, with a typed, lossless string representation.
Repository Health
Technical Analysis
radix is a small, dependency-free Rust crate for converting unsigned integers between number bases from 2 to 36. It defines a single RadixNum enum with one variant per supported radix, so a value’s base travels with its type instead of being tracked separately by the caller.
The crate covers the common conversion paths: parsing a string in an arbitrary radix, re-encoding a value into a different radix, formatting with the conventional 0b/0/0x prefixes for bases 2, 8, and 16, and iterating over a number’s individual digits. All of Rust’s unsigned integer primitives (u8 through u128, usize) convert into a RadixNum via the standard From trait.
What You Get
- RadixNum enum - one variant per supported radix (2 through 36), each wrapping the digit string for that base
- Bidirectional conversion -
from_strparses a string in a given radix;with_radixre-encodes an existing value into a different radix - Idiomatic Display formatting - prints with
0b,0, or0xprefixes for binary, octal, and hex respectively, and a<digits>r<radix>form for every other base - Digit iteration -
digits()returns an iterator over a number’s individual characters for custom rendering or analysis - From impls for every unsigned integer type -
u8,u16,u32,u64,u128, andusizeall convert into a base-10RadixNumfor free
Common Use Cases
- Base conversion utilities - a CLI or library needs to accept numbers in hex/octal/binary and print them back out in another base
- Custom numeral systems - encoding IDs or tokens in a non-standard radix (e.g. base 32 or base 36) for compact string representation
- Teaching and demo tools - illustrating positional numeral systems and radix arithmetic in an educational context
- Debug and log formatting - rendering integers in hex or binary for logs and diagnostics using the crate’s Display impl
Under The Hood
Architecture
The crate is a single file (src/lib.rs) exposing a public RadixNum enum with one variant per supported radix (Radix2 through Radix36), a RadixErr error enum, and a handful of private helpers (validate_radix, validate_base, dec_to_radix_x, radix_x_to_dec). Every conversion funnels through decimal as an intermediate representation: from_str parses a string into decimal via radix_x_to_dec, then with_radix walks it back out to the target base via dec_to_radix_x. There is no I/O, no external state, and no dependency injection — just pure, deterministic string transformations. The one architectural rigidity is the one-variant-per-radix design: adding a new base or a signed representation means touching every exhaustive match arm across with_radix, radix, and as_str.
Tech Stack
Cargo.toml declares zero dependencies beyond the Rust standard library, targeting the 2018 edition on Rust >= 1.26. There is no build script and no external crates used even for testing — tests rely purely on std assertions inside an inline #[cfg(test)] module. CI is a single GitHub Actions workflow (.github/workflows/rust.yml) that runs cargo build --verbose and cargo test --verbose on push against ubuntu-latest. There is no publish/release automation and no committed Cargo.lock, which is correct convention for a library crate.
Code Quality
Tests live inline in src/lib.rs and are extensive for the crate’s scope: every supported radix from 2 through 36 has its own dedicated test converting a fixed set of decimal inputs and asserting the exact digit string, plus explicit tests for rejected radices (0, 1, 37) and empty/invalid input. There are no integration tests, no doctests beyond the module-level doc comment, and no linter or formatter configuration committed to the repo, nor a lint step in CI. Error handling in the public API is explicit and typed via RadixErr (which implements std::error::Error and Display) rather than panics, and internal helpers propagate errors with ? rather than .unwrap().
API Design
The public surface is small and easy to learn: a single RadixNum type with from_str, with_radix, as_str, as_decimal, digits, and radix methods, plus From impls for every unsigned integer primitive so RadixNum::from(255u8) works without any ceremony. Display mirrors Rust’s own integer-literal conventions by prefixing binary/octal/hex output with 0b/0/0x. The tradeoffs are that encoding the radix into the enum variant means the type can’t represent an arbitrary runtime radix without an exhaustive match, there is no direct conversion between two non-decimal radices without passing through decimal, and the crate hasn’t been touched since 2019 — a modern rewrite would likely collapse the 36 enum variants into a single newtype carrying the radix as data.