smol_str
A small-string-optimized, immutable Rust string type with O(1) clone and no heap allocation for short strings.
Repository Health
Technical Analysis
smol_str is a compact, immutable string type for Rust built for situations where cheap cloning matters more than in-place mutability. It stores strings up to 23 bytes entirely on the stack, matching size_of::<String>() at 24 bytes, and falls back to a heap-allocated, reference-counted Arc<str> for longer content so that clones are O(1) instead of a fresh allocation and copy. It also special-cases whitespace-heavy strings — runs of newlines followed by spaces — so indentation-heavy source text stays inline even past the normal 23-byte threshold.
The crate originated inside rust-analyzer as the token and identifier storage type for a language server that clones the same short strings constantly while walking syntax trees, and it now ships as an independently versioned crate published from the rust-analyzer Cargo workspace. It is no_std-compatible (via alloc) and offers optional serde, borsh, and arbitrary integrations behind Cargo feature flags.
What You Get
- A
SmolStrtype with O(1)Clonevia inline storage orArc<str>sharing, sized identically tostd::String SmolStrBuilderfor incrementally constructing aSmolStrviapush/push_strwithout intermediateStringallocationsToSmolStrandStrExtextension traits for ergonomic conversions and allocation-avoiding case-conversion/replace helpers- Optional
serdeandborsh(de)serialization support plus anarbitraryintegration for fuzzing, all gated behind Cargo features - A
#![no_std]-compatible build (withalloc) for use outsidestdenvironments
Common Use Cases
- Interning tokens and identifiers in a compiler, parser, or language server where the same short strings are cloned repeatedly while walking a syntax tree
- Storing string keys in large in-memory data structures where per-clone heap allocation would dominate runtime
- Representing string literals and constant text from source code without allocating
- Passing string data across API boundaries where cheap, shared ownership beats
String’s exclusive-ownership clone cost
Under The Hood
Architecture
SmolStr wraps a private Repr enum with three variants — Inline (a fixed byte buffer with a packed length tag), Static (a &'static str reference), and Heap (an Arc<str> for cheap shared cloning) — and every public operation dispatches through Repr’s own methods. The public surface (SmolStr, SmolStrBuilder, ToSmolStr, StrExt) lives in a single flat src/lib.rs, with serde.rs and borsh.rs split out behind feature gates. Because Display, Deref<Target=str>, equality, hashing, and the builder all funnel through Repr::as_str()/Repr::new(), the core enum is the crate’s single point of failure — the test suite explicitly locks size_of::<SmolStr>() to size_of::<String>(), so any change to the inline byte-packing scheme or the 23-byte threshold risks breaking that invariant silently.
Tech Stack
Pure Rust, no_std-compatible via the alloc crate, with a minimal dependency surface: serde_core, borsh, and arbitrary are all optional and feature-gated, while dev-dependencies (proptest, serde_json, serde, criterion, rand) are used only for tests and benchmarks. The crate is a member of rust-analyzer’s Cargo workspace (lib/*) but is versioned and published to crates.io independently of the rust-analyzer binary itself, targeting a rolling MSRV pinned to the latest stable toolchain.
Code Quality
A dedicated test suite covers conversions, size invariants, and builder behavior, including property-based tests via proptest for construction and equality; a separate tidy.rs test enforces repository formatting hygiene. The implementation leans heavily on unsafe for the inline byte-buffer transmutation and length packing, but each use carries an explicit // SAFETY: comment documenting the invariant it relies on. Criterion benchmarks track the crate’s hot paths — clone, case conversion, and replace — guarding against performance regressions, and the crate is exercised as part of rust-analyzer’s workspace-level CI.
API Design
The public API keeps construction paths explicit rather than hiding them behind one polymorphic constructor: new, new_inline, and new_static each make a different cost/behavior trade-off clear at the call site. SmolStrBuilder mirrors String’s familiar push/push_str shape, and the ToSmolStr/StrExt extension traits let existing &str-oriented code opt into allocation-avoiding case conversion and replace operations without new free functions. Broad From/Deref<Target=str> coverage means SmolStr mostly drops into existing string-based APIs with little friction; the main developer cost is accepting immutability and routing in-place edits through SmolStrBuilder instead.