strfmt
Python-style dynamic string formatting for Rust, using a HashMap of named keys instead of compile-time literals.
Repository Health
Technical Analysis
strfmt brings Rust’s std::fmt formatting syntax to strings that aren’t known at compile time. Rust’s built-in format! macro requires the format string to be a literal, which makes it unusable for things like user-supplied templates or values read from a configuration file. strfmt fills that gap: it parses a runtime &str containing {key}-style placeholders and substitutes values from a HashMap, supporting the same alignment, fill, sign, width, precision, and type specifiers as std::fmt.
The crate exposes a strfmt function plus a Format trait that adds a .format(&vars) method directly onto str and String, so formatting reads naturally as "hi {name}".format(&vars). A lower-level strfmt_map function and DisplayStr trait let callers plug in custom per-key formatting logic (e.g. to format integers and floats via a closure) instead of relying on Display. It has no dependencies beyond the Rust standard library.
Development has slowed and the maintainer has stated in the README that they are no longer an active Rust developer, but still reviews and merges pull requests for bug fixes and the outstanding std::fmt feature parity items tracked in the README’s status checklist (sign-aware zero padding, unbounded numeric types, Vec<Display> formatting).
What You Get
- A
strfmt(fmtstr, &vars)function that formats a runtime string using aHashMapof named values - A
Formattrait adding.format(&vars)directly tostrandStringfor a fluent call style - Support for the same alignment, fill character, sign, width, precision, and type specifiers as Rust’s native
std::fmtsyntax - A
DisplayStrtrait so custom types can define exactly how they render into aFormatter, independent ofstd::fmt::Display - A lower-level
strfmt_mapentry point plus aFormattertype for building custom per-key formatting callbacks (e.g. formatting numeric types beyond the built-inDisplay-based path) - Zero runtime dependencies — pure Rust standard library
Common Use Cases
- Rendering user-configurable message or notification templates stored as plain strings
- Formatting values read from configuration files where the format string itself is data, not a compile-time literal
- Building simple templating for CLI output or log message formats without pulling in a full templating engine
- Reusing familiar
std::fmt-style width/precision/alignment syntax for dynamic strings instead of hand-rolling padding logic
Under The Hood
Architecture
strfmt is organized as a small set of tightly-scoped modules under src/: types.rs defines the shared Alignment, Sign, and FmtError enums; formatter.rs implements Formatter::from_str, which splits a {key:spec} token into its identifier and a format-spec tail and parses that tail with parse_like_python — logic explicitly ported from CPython’s formatter_unicode.c; fmtstr.rs and fmtnum.rs (via the fmtint!/fmtfloat! macros in macros.rs) then apply that parsed spec to strings and numeric primitives respectively, writing into a caller-supplied &mut String buffer through fmt::Write. The public entry point in lib.rs (strfmt) walks the input string once, splitting on {/}, constructing a Formatter per placeholder, and dispatching to a closure that looks the key up in the caller’s HashMap and calls .display_str(); strfmt_map exposes the same loop with the substitution closure exposed directly, which is what lets Formatter-level numeric formatting (f.f64, f.i64) live outside the crate’s own key-lookup logic. The design is a straightforward single-pass parser/interpreter with no intermediate AST — changing the core token-splitting logic in lib.rs or the spec-parsing state machine in formatter.rs would ripple through every formatting call.
Tech Stack
The crate targets stable Rust 2015/2018-era idioms (edition unspecified in Cargo.toml, tested against stable/beta/nightly per .travis.yml) and declares zero runtime dependencies — [dependencies] in Cargo.toml is empty. It relies entirely on std::collections::HashMap, std::fmt::Write, and std::str::FromStr. There is no build tooling beyond Cargo itself, no async runtime, no FFI, and no external formatting crate — the Python-format-spec grammar is hand-reimplemented rather than pulled in from a dependency.
Code Quality
Tests are colocated under src/tests/ (strfmt.rs, key.rs, float.rs, legacy.rs, fmt.rs, macros.rs, test_trait.rs) and exercised via #[cfg(test)] mod tests; in lib.rs, giving reasonably thorough coverage of the format-spec parsing and key-lookup error paths (FmtError::Invalid/KeyError/TypeError). Error handling is explicit and typed via the FmtError enum rather than panics, though formatter.rs contains a few internal .unwrap() calls on writes that are expected to be infallible. There is no CI badge beyond the legacy .travis.yml (no active GitHub Actions workflow found), no clippy/rustfmt configuration checked in, and the maintainer states in the README they are no longer actively developing Rust, so quality is maintained mainly through community pull requests.
API Design
The public surface is small and idiomatic: a free function (strfmt), a trait extension (Format on str/String) for fluent call sites, and a lower-level escape hatch (strfmt_map/DisplayStr) for custom numeric formatting — mirroring the shape of std::fmt closely enough that anyone familiar with Rust’s or Python’s format-spec syntax needs no new mental model. Documentation is doc-tested (examples in lib.rs compile via cargo test), and the README is thorough about status, limitations (no empty {} identifiers, beta numeric support), and a public roadmap checklist, which lowers the surprise factor for adopters despite the project’s low current maintenance velocity.