Installation
[]
= "0.3"
Quick start
use Interner;
let mut interner = new;
// Intern returns a small Copy handle; the same string always returns the same one.
let while_kw = interner.intern;
let for_kw = interner.intern;
assert_eq!;
// Name comparisons are now integer comparisons, not byte walks.
assert_ne!;
// Resolve borrows the original bytes straight out of the store.
assert_eq!;
Folding a token stream down to symbols, so later passes compare integers and a
name in an AST node costs four bytes instead of an owned String:
use ;
let mut interner = new;
let source = ;
let tokens: = source.iter.map.collect;
// The three `x` occurrences collapsed to one symbol.
assert_eq!;
assert_eq!; // let, x, =, +
Features
- Four-byte handle. A
Symbolis aNonZeroU32newtype —Copy, integer equality / ordering / hashing, andOption<Symbol>is four bytes too. - Bytes stored once. Interned strings are appended end to end in a single contiguous buffer; the dedup index stores symbol ids, not a second copy.
- Allocation-free hits. Interning a string already seen is a hash lookup with no allocation and no copy.
- Growth-stable symbols. A symbol keeps resolving to the same string for the interner's whole lifetime, including after the backing store reallocates.
- Thread-safe variant.
ConcurrentInternerlets many threads intern into one shared symbol space; the warm read path runs concurrently and racing threads never mint a duplicate symbol. Both interners share theLookupread trait. no_std. Relies only onalloc; the defaultstdfeature is additive. No runtime dependencies beyond an optionalserde(planned).#![forbid(unsafe_code)]. The contiguous store is implemented without anyunsafe.
Performance
Resolution is a side-table index plus a slice — measured at roughly 1.1 ns per
resolve (Criterion mean, Windows x86_64, Rust stable, on the development
machine). Interning an already-seen string is allocation-free: a hash over the
bytes, an open-addressing probe, and one byte comparison to confirm the hit. The
numbers below are a v0.x baseline, tracked over time rather than advertised as
final:
| Operation | Mean |
|---|---|
resolve (id → &str) |
~1.1 ns |
intern (repeat hit, no allocation) |
~0.23 µs |
intern (new string, amortised growth) |
~0.62 µs |
Run them yourself with cargo bench. The warm read path scales across threads:
at 8 threads the ConcurrentInterner sustains roughly 4× the single-thread intern
throughput, since hits are served under a shared read lock.
Concurrent interning
use Arc;
use thread;
use ConcurrentInterner;
let interner = new;
let handles: =
.map
.collect;
let symbols: = handles.into_iter.map.collect;
// Every thread agrees on one symbol; "shared" was interned exactly once.
assert!;
assert_eq!;
API overview
For the complete reference with examples, see docs/API.md.
Symbol— four-byteCopyhandle to an interned string.Interner— single-threaded interner:intern,get,resolve,len,with_capacity.ConcurrentInterner— thread-safe interner sharing one symbol space (requires thestdfeature).Lookup— read-side trait both interners implement.
Status
v0.3.0 ships the core interner, the symbol, and the thread-safe
ConcurrentInterner. Optional serde for Symbol and the defined
symbol-space-exhaustion result land across the remaining 0.x series per the ROADMAP; the public surface freezes at 1.0.0.
Contributing
See dev/DIRECTIVES.md for engineering standards and the definition of done. Before a PR: cargo fmt --all, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all-features must be clean.