itoa

Fast, allocation-free conversion of integer primitives to decimal strings for Rust.

Library
Cargo
v1.0.18
378stars
MIT OR Apache-2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
54/100Fair
Development Activity32
Maintenance40
Community64
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
85/100Excellent
Architecture92
Code Quality93
Innovation88
Learning Curve65

itoa is a small, no_std-compatible Rust crate that converts integer primitives directly into decimal string representations without going through core::fmt::Formatter. Written by David Tolnay, it ports the digit-formatting routine from Rust’s own standard library and strips away the overhead of the generic formatting machinery, writing into a stack-allocated buffer via a two-digit lookup table so no heap allocation is ever required.

It underpins performance-sensitive code across the Rust ecosystem — serde_json and many other serialization and logging crates depend on itoa internally whenever they need to turn a u64 or i128 into text as fast as possible.

What You Get

  • A Buffer struct sized exactly to the largest possible decimal representation for any Rust integer type, allocated entirely on the stack
  • A sealed Integer trait implemented for every primitive integer type (i8 through i128, u8 through u128, isize/usize)
  • A 200-byte two-digit decimal lookup table plus a batched digit-writing algorithm that formats four digits per iteration for large integers
  • An optional no-panic feature flag that statically verifies at compile time that formatting can never panic

Common Use Cases

  • Serializing JSON, CSV, or other text formats where integer fields need fast, allocation-free stringification (used internally by serde_json)
  • Building high-throughput logging or metrics pipelines where converting numeric values to strings sits on a hot path
  • Embedded or no_std environments where heap allocation for basic number formatting isn’t available or desirable
  • Writing custom binary-to-text encoders or protocol implementations that need predictable, allocation-free integer formatting

Under The Hood

Architecture The crate is deliberately minimal — just src/lib.rs plus a small src/u128_ext.rs helper. The public surface is a Buffer struct wrapping a MaybeUninit<u8> array sized to i128::MAX_STR_LEN, paired with a sealed Integer/private::Sealed trait pair implemented per primitive type through macros (impl_Integer!, impl_Integer_size!) so downstream crates cannot add conflicting implementations. An internal Unsigned trait does the real work in fmt(), implemented per unsigned width via impl_Unsigned! for u8/u16/u32/u64 and hand-written for u128 (which needs 128-bit division helpers in u128_ext.rs implementing the Granlund-Montgomery multiplicative-division algorithm). Formatting flows from Buffer::format(i) into Sealed::write, which for unsigned values calls Unsigned::fmt to write digits back-to-front using the DECIMAL_PAIRS lookup table (four digits, then two, then one), and for signed values computes unsigned_abs(), delegates to the same routine, then prepends a - if negative. Every path terminates in an unsafe slice_buffer_to_str that reinterprets the initialized buffer suffix as UTF-8 — correctness rests entirely on the invariant that every byte from offset onward has actually been written as an ASCII digit.

Tech Stack Pure Rust, edition 2021, MSRV 1.68, with zero required runtime dependencies. The only dependency is the optional no-panic crate (v0.1), gated behind a feature flag for compile-time panic-freedom verification; criterion 0.8 is a dev-dependency used in benches/bench.rs with a custom (harness = false) Criterion harness. #![no_std] sits at the top of lib.rs, so the crate has no allocator or OS dependency at all. CI (.github/workflows/ci.yml) runs a matrix across nightly/beta/stable/MSRV Rust, plus dedicated jobs for a docs.rs build, Miri (undefined-behavior detection under strict provenance), Clippy with pedantic lints, cargo fuzz check, and cargo outdated. The crate ships to crates.io as a leaf dependency consumed by other libraries.

Code Quality Tests in tests/test.rs use a table-driven test! macro checking boundary values (zero, MAX, MIN) across u64/i64/i16/u128/i128, plus an explicit test asserting the MAX_STR_LEN constant for every integer type. Beyond unit tests, correctness is reinforced by cargo miri test in CI (essential given the crate’s extensive unsafe/MaybeUninit use), a dedicated fuzz/ target, and the optional no-panic feature that proves at compile time that no code path can panic. There is no Result-based error handling anywhere — correctness is enforced by types and by // SAFETY: comments justifying every unsafe block rather than runtime checks. Clippy runs in CI with -Dclippy::all -Dclippy::pedantic, and the test suite is also exercised with --no-default-features and --release builds. This is an unusually rigorous verification stack for a crate this size.

API Design The public API is intentionally tiny: Buffer::new() plus buffer.format(value) returning a &str borrowed from the buffer, mirroring the ergonomics of itoa’s sibling crate ryu (float formatting). There’s no configuration and no required dependencies, so getting from an integer to a formatted string takes two lines with no setup. Documentation is a single top-level //! doc comment with a working example, cross-linked to docs.rs, plus a benchmark chart comparing itoa against other Rust integer-formatting approaches. The one ergonomic cost is that the returned &str borrows from the Buffer, so callers must keep the buffer alive across the call — a small, well-documented tradeoff typical of allocation-free Rust APIs.

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