CheetahString is an immutable, clone-cheap UTF-8 value for latency-sensitive
systems. It stores short text inline, keeps static text allocation-free, and
shares long dynamic text through Arc<str>. The same value contract works with
std and no_std + alloc.
Version 3.1.0 is the supported 3.x release line for the immutable
architecture.
| Input path | Storage | Allocation events during conversion | Clone allocation |
|---|---|---|---|
Explicit from_static_str |
Static | 0 | 0 |
| Other UTF-8 input β€ 23 bytes | Inline | 0 | 0 |
Long Arc<str> |
Shared | 0; payload pointer is retained | 0 |
Long borrowed text or exact-capacity String |
Shared | 1 | 0 |
Long spare-capacity String / builder |
Shared | 2: shrink/reallocate, then Arc backing | 0 |
The representation has no mutable Owned(String) state. Construction history
therefore cannot change clone complexity. Use:
CheetahStringfor protocol text, immutable fields, and collection keys;CheetahBuilderfor append-heavy construction followed byfinish();- standard
Stringwhen mutation or spare capacity must continue; CheetahBytesfor byte semantics when the optionalbytesfeature is active.
Add the crate to your project:
[dependencies]
cheetah-string = "3.1.0"With optional integrations:
[dependencies]
cheetah-string = {
version = "3.1.0",
features = ["serde", "bytes"]
}The minimum supported Rust version is 1.95.
The packaged consumer matrix can be reproduced with
bash scripts/check-msrv-package.sh 1.95 on Unix or
pwsh -File scripts/check-msrv-package.ps1 -Msrv 1.95 on Windows.
use cheetah_string::{CheetahBuilder, CheetahString};
let inline = CheetahString::from("orders");
let static_value = CheetahString::from_static_str("system-topic");
let shared = CheetahString::from_string("long-dynamic-value-".repeat(8));
let adopted = CheetahString::from(std::sync::Arc::<str>::from(
"ownership-preserving-shared-value",
));
let cloned = shared.clone();
assert_eq!(inline, "orders");
assert_eq!(static_value, "system-topic");
assert_eq!(shared, cloned);
assert_eq!(shared.as_bytes().as_ptr(), cloned.as_bytes().as_ptr());
assert_eq!(adopted, "ownership-preserving-shared-value");
let mut builder = CheetahBuilder::with_capacity(64);
builder.push_str("orders");
builder.push('@');
builder.push_str("group-a");
let route_key = builder.finish();
assert_eq!(route_key, "orders@group-a");When mutation continues, keep the builder's String:
use cheetah_string::CheetahBuilder;
let mut builder = CheetahBuilder::with_capacity(128);
builder.push_str("orders");
let mut value = builder.into_string();
value.push_str("@group-a");Equality, prefix, and suffix checks use Rust's portable slice/str paths.
Substring search uses memchr/memmem.
Iterator capabilities are explicit:
use cheetah_string::CheetahString;
let value = CheetahString::from("a::b::c");
let forward: Vec<_> = value.split_str("::").collect();
assert_eq!(forward, ["a", "b", "c"]);
let csv = CheetahString::from("a,b,c");
let reverse: Vec<_> = csv.split_char(',').rev().collect();
assert_eq!(reverse, ["c", "b", "a"]);
let reverse_lines: Vec<_> = CheetahString::from("a\nb\nc").lines().rev().collect();
assert_eq!(reverse_lines, ["c", "b", "a"]);split_str is intentionally forward-only. Unsupported reverse iteration fails
at compile time instead of panicking at runtime.
The ownership boundary is explicit:
| Conversion | UTF-8 validation | Payload copy |
|---|---|---|
bytes::Bytes -> CheetahBytes |
No | No |
CheetahBytes -> bytes::Bytes |
No | No |
Bytes -> CheetahString::try_from |
Yes | Yes |
CheetahBytes -> CheetahString::try_from |
Yes | Yes |
Bytes -> CheetahString::try_copy_from_bytes |
Yes | Yes |
&CheetahBytes -> try_copy_to_cheetah_string |
Yes | Yes |
use bytes::Bytes;
use cheetah_string::{CheetahBytes, CheetahString};
let raw = Bytes::from_static(b"orders");
let bytes = CheetahBytes::from(raw);
let text = bytes.try_copy_to_cheetah_string().unwrap();
assert_eq!(text, "orders");
let invalid = Bytes::from_static(&[0xff]);
let error = CheetahString::try_copy_from_bytes(invalid.clone()).unwrap_err();
assert_eq!(error.into_bytes(), invalid);The conversion matrix above is covered by tests/bytes.rs and
tests/allocation_contract.rs.
| Feature | Default | Contract |
|---|---|---|
std |
Yes | Standard-library integration |
serde |
No | Serialization and deserialization |
bytes |
No | CheetahBytes and explicit byte/text conversion |
experimental-simd |
No | Isolated x86_64 SSE2 benchmark path; not recommended for production |
simd |
No | Deprecated alpha compatibility alias for experimental-simd |
experimental-packed |
No | Deprecated no-op retained for 3.x dependency compatibility |
Optional features do not change the stable CheetahString layout.
The former packed v1 type was removed in 3.1 because its heap representation
round-tripped an allocation pointer through usize, which strict-provenance
Miri rejected. There is no safe 24-byte drop-in replacement. Use
CheetahString for immutable text or CheetahBuilder/String while mutation
continues.
The repository includes RocketMQ-shaped Criterion workloads for property building, remoting-header parsing, topic insertion and lookup, plus explicit layout and allocation contracts. Blocking timing decisions run only on a dedicated fixed CPU with two reversed base/head rounds.
cargo test --test layout_snapshot --all-features
cargo test --test allocation_contract --all-features -- --test-threads=1
cargo bench --bench shared_backing -- __allocation_evidence_only__ --noplot \
2>&1 | tee target/allocation-evidence.log
python scripts/verify-allocation-evidence.py target/allocation-evidence.log
cargo bench --bench comprehensive
cargo bench --bench mq_properties
cargo bench --bench mq_remoting_header
cargo bench --bench mq_topicHosted-runner and local benchmark results are diagnostic; they do not independently establish a release-grade performance pass. The versioned allocation and layout tests are the deterministic performance contracts. See Performance contracts for the exact enforced budgets and the distinction between deterministic gates and diagnostic timing results.
The repository's workflows are the authoritative record of automated checks. Release validation is fail-closed: formatting, linting, tests, dependency audit, and package construction must complete before any tag or publication step.
The unsafe constructors are explicitly named and require the caller to prove UTF-8 validity. Safe byte constructors validate before creating text.
CI enforces the Rust 1.95 packaged-consumer matrix, warning-free rustdoc, locked dependency auditing, and repository workflow contracts. The Safety workflow runs Miri over the stable text/byte invariants and compiles every libFuzzer target with AddressSanitizer on pull requests and on a weekly schedule. See Safety model for the maintained unsafe-boundary inventory and local verification commands.
Licensed under either of Apache License 2.0 or MIT, at your option.