smart-format 0.1.0

Composable, zero-allocation Display formatting combinators for Rust.
Documentation

MSRV 1.85

Smart Format

Composable, zero-allocation Display formatting for Rust.

smart-format provides extension traits that add formatting operations to any type implementing Display. Each operation returns a lightweight wrapper that itself implements Display, so operations compose through fmt::Formatter without intermediate String allocations.

Why this exists

Rust's Display trait is a streaming, zero-alloc formatting interface. But composing Display values is awkward — there's no built-in vocabulary for "escape this, then truncate, then wrap in tags." People end up calling .to_string() at every step, allocating intermediate Strings that only exist to be formatted again.

This crate makes the composition part zero-alloc too. Every operation is a struct wrapping T: Display that implements Display with a transformation. Chain as many as you want.

Features

Core functionality (always available):

Trait Methods
SmartFormat display_wrap(), display_prefix(), display_suffix(), display_if(), display_or_if(), display_truncate(), display_truncate_with(), display_pad_left(), display_pad_right()
SmartFormatCase capitalize(), lowercase(), uppercase()
DisplayIterator display_concat(), display_join(sep)

Feature-gated transforms:

Feature Trait Methods
html SmartFormatHtml html_escape(), html_escape_strict(), html_unescape_basic(), html_attribute(name)
url SmartFormatUrl url_escape()
xml SmartFormatXml xml_c_data()
full enables all of the above

Usage

[dependencies]
smart-format = { version = "0.1", features = ["html"] }
use smart_format::prelude::*;

// Combinators chain without allocation
let output = "hello world"
    .display_truncate(5)
    .display_pad_right(10, '.')
    .display_wrap("[", "]")
    .to_string();
assert_eq!(output, "[hello.....]");

// Case transforms
let title = "hello world".capitalize().to_string();
assert_eq!(title, "Hello world");

// Iterator formatting
let joined = ["a", "b", "c"].iter().display_join(", ").to_string();
assert_eq!(joined, "a, b, c");

// Left-pad (zero-alloc, two-pass)
assert_eq!("007", 7.display_pad_left(3, '0').to_string());

With the html feature:

use smart_format::prelude::*;

// Escape, truncate, wrap — all composed, zero intermediate allocations
let safe = "<script>alert('xss')</script>"
    .html_escape()
    .display_truncate(20)
    .display_wrap("<span>", "</span>")
    .to_string();

Design decisions

Zero-alloc by construction. Every combinator is a struct wrapping T: Display that implements Display. No heap allocation happens until someone calls .to_string() or writes to a sink. This is the same pattern used by Iterator adapters.

Feature flags for domain-specific transforms. HTML/URL/XML escaping adds dependencies and code that many users don't need. Core text operations (case, iteration) are always available because they have no extra dependencies and are universally useful.

Unicode scalar values for width. Width-sensitive operations (future: pad, truncate) count Unicode scalar values (char), not bytes or grapheme clusters. This is documented as a known limitation. Grapheme-aware and terminal-width-aware variants may be added behind a feature flag.

Streaming escaping via bitset. HTML escape uses a u128 bitmask for O(1) "needs escaping?" per ASCII byte. The bitmask is computed at compile time. Non-ASCII bytes pass through unchanged.

MSRV

Rust 1.85.0 (edition 2024).

License

Licensed under either of

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Development

Recommended (enable repo hooks once per clone):

git config core.hooksPath .githooks

Quality gates:

cargo +nightly fmt --all -- --check
cargo check --all-targets
cargo test --all-features
cargo clippy --all-targets -- -D warnings

See also: CONTRIBUTING.md.