compact_str: 24 bytes on the stack, and the same size as String
A memory efficient string type that can store up to 24* bytes on the stack
At a glance
- What is it?
- compact_str is a MIT licensed Rust string type that stores short strings inline, keeps the same size as String, and swaps to the heap only when the data no longer fits, which makes it a natural fit for parsing and deserialisation workloads full of small strings. The judgement is that the size guarantees and the growth rate are the real argument, and that the feature list is broader than most people expect from a string type.
- Who is it for?
- compact_str fits a Rust service that parses or deserialises large volumes of short strings, especially one where the string type is stored in a struct by value, since matching the size of String means no struct grows and the Option niche means an optional field costs nothing extra.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 20 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Small string optimisation, expressed as a Rust type
The one line summary in the README is that a CompactString is a more memory efficient string type that can store smaller strings on the stack and transparently stores longer strings on the heap, which it names as a small string optimisation. The threshold is 24 bytes on the stack, and the README footnotes that this is 12 bytes for 32-bit architectures, so the inline capacity tracks the pointer width of the target. Longer strings are stored on the heap. The type is meant to be used mostly as a drop in replacement for String, and the README points at the workloads where that pays off, parsing, deserialising, or any other application where you may have smaller strings. The reasoning is straightforward. A String is three words on a 64-bit target, a pointer, a length and a capacity, and it always points at heap memory even when the content is two characters long. CompactString keeps the same three words and uses them differently, so a short string occupies no allocation at all.
The size guarantees are the whole point
The Properties section is a list of guarantees, and the first one is the one that makes adoption painless. The relevant assertions are these:
size_of::<CompactString>() == size_of::<String>()
size_of::<CompactString>() == size_of::<Option<CompactString>>()So a struct field changes type without changing size, and an optional field costs nothing beyond the string itself. The rest of the list describes the behaviour you get in exchange. Clone is O(n), which is what a copying string type should be. From<String> and From<Box<str>> re-use the underlying buffer rather than copying it, and they eagerly inline small strings. Construction from a &'static str is O(1) through CompactString::const_new. The heap based string grows at a rate of 1.5 times where the standard library String grows at 2 times, which is a real difference in reallocation traffic for long strings. String accesses use branchless instructions, and the copy for small and medium strings is specialised, using two overlapping fixed-width load and store operations so that creating or cloning a short string avoids a function call round trip for memcpy. The crate also supports no_std environments.
Two traits and a macro cover most call sites
The crate exposes two traits and one macro, and they are the entire public surface beyond the type itself. ToCompactString provides the to_compact_string method for converting types into a CompactString, and it is automatically implemented for every type that implements std::fmt::Display, with higher performance implementations for the common cases: the unsigned and signed integer types including usize and isize and u128, the float types, bool and char, the NonZero integer types, and String and CompactString themselves. CompactStringExt provides join_compact with a separator and concat_compact without one, and it is implemented for anything that can be turned into an iterator of AsRef<str> types, so vectors, slices and other collections can be joined into a CompactString. Then there is format_compact!, a macro that builds a CompactString from arguments the way std::format! builds a String. For a codebase migrating from String, the two traits cover the conversions that would otherwise need a to_string call at every site.
The feature list is a map of the Rust ecosystem
The optional features are where this crate connects to the rest of the ecosystem, and there are a lot of them. serde adds Serialize and Deserialize for CompactString. bytes provides from_utf8_buf and from_utf8_buf_unchecked to build a CompactString from a bytes::Buf. markup implements the Render trait so the values can be used in templates as HTML escaped strings. diesel allows CompactStrings in text columns, and sqlx-mysql, sqlx-postgres and sqlx-sqlite do the same for sqlx. For fuzzing there is arbitrary, proptest and quickcheck, each implementing its own Arbitrary trait. rkyv adds Archive, Serialize and Deserialize for zero-copy serialisation, interchangeable with serialised Strings, and smallvec adds an into_bytes method that converts a CompactString into a byte vector. valuable implements the object-safe Valuable trait, pyo3 implements FromPyObject and IntoPyObject so the values convert to and from Python strings, schemars produces the same JSON schema as a String, garde implements the validation rule traits, borsh implements the binary format traits, zeroize allows the contents to be wiped from memory, and defmt implements the defmt Format trait. The practical upshot is that a CompactString can sit in a struct that is serialised, validated, stored in a database, exposed to Python and still cleared on drop.
The examples directory is the feature documentation
The repository root carries a workspace whose members are the bench crate, the fuzz crate and a set of example crates, one per integration, with the compact_str directory itself excluded from the root workspace. The examples cover bevy-reflect, bytes, diesel, garde, macros, pyo3, schemars, serde, sqlx, traits, utoipa and valuable, and the list mirrors the feature flags almost one to one. That is a deliberate structure. Each example crate is a small, buildable proof that the integration works against a real dependency, and reading a dozen short crates is faster than reading a dozen paragraphs of documentation. The workspace also contains a bench crate, a fuzz directory, a CHANGELOG.md, a Cross.toml file for cross compilation targets, a rustfmt configuration and a .rustfmt.toml at the root, and the top level .github directory holds the workflow files.
Where the inline threshold stops helping
The limits follow from the design rather than from a defect. Anything longer than 24 bytes lives on the heap, so a workload of medium length strings, in the thirty to eighty byte range that is common in identifiers and URLs, gets the CompactString layout of three words plus a heap allocation plus a 1.5 times growth factor, which is not obviously better than a plain String and may be marginally worse while the string is being built. The 32-bit case is tighter still, since the inline capacity halves to 12 bytes. There is a second, quieter cost, and it comes from the fact that the type is not a String in disguise: it is a distinct type with its own trait surface, so a codebase that passes strings to third party APIs expecting &str or String needs conversion at the boundary, and a codebase that pattern matches on String needs its match arms updated. The no_std support also means the feature choices have to be made deliberately, since a no_std target has no allocator available for the heap path at all unless one is provided.
Continuous integration that checks the floor as well as the code
The badges at the top of the README tell you what the project checks on every push, and they cover more than the usual compile and test. There is a continuous integration workflow, a cross platform workflow, a minimum supported Rust version workflow, and a Clippy workflow, and the crate advertises a minimum supported Rust version of 1.71 in its badge, which is why the MSRV job exists as a separate check rather than being folded into the main build. The licence is MIT, with the licence file at the root of the repository, and the licence badge on crates.io agrees. On the release side, the repository publishes no GitHub releases, so the crate versions on crates.io are the release channel and the CHANGELOG.md file is the history. The last push was on 2026-10-03 and the repository is not archived, so work is still landing on the main branch. The fuzz directory and the three fuzzing feature flags show where the effort goes next, since a string type that is used in parsers is exactly the kind of code that needs a fuzzer behind it.
Editorial conclusion
compact_str fits a Rust service that parses or deserialises large volumes of short strings, especially one where the string type is stored in a struct by value, since matching the size of String means no struct grows and the Option niche means an optional field costs nothing extra. It is the wrong choice for long strings, since anything past 24 bytes goes to the heap with a 1.5x growth factor, and it is a poor fit if you need a format string or a string builder rather than a value type. Before you adopt it, measure the actual length distribution of your strings, and check the MSRV badge against your toolchain, since the crate declares Rust 1.71 and the repository runs a dedicated workflow for that floor.
Frequently asked questions
How much data can a CompactString hold on the stack?
Up to 24 bytes on a 64-bit target, and 12 bytes on a 32-bit architecture, according to the README footnote. Strings longer than that are stored on the heap, with the heap allocation growing at a rate of 1.5 times.
Is CompactString the same size as String?
Yes. The documented properties state that size_of::<CompactString>() equals size_of::<String>(), and that it equals size_of::<Option<CompactString>>() as well, so an optional field costs nothing beyond the string itself.
How do I build a CompactString from a number or from a list of strings?
The ToCompactString trait gives you to_compact_string on any Display type, with faster implementations for integers, floats, bool, char and the NonZero types. The CompactStringExt trait gives you join_compact with a separator and concat_compact without one for any collection of AsRef<str> items, and the format_compact! macro builds a value from format style arguments.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/parkmycar-compact-str)Was this useful?