pub struct ConcurrentInterner { /* private fields */ }std only.Expand description
A thread-safe string interner that many threads can intern into at once.
ConcurrentInterner wraps the single-threaded Interner in an RwLock and
exposes the same operations through &self, so a pool of lexer or parser
threads can share one symbol space. It is additive, not a rewrite: the storage,
the dedup index, and the symbol stability guarantees are exactly those of
Interner; this type only adds the synchronisation.
§Correctness under contention
Interning takes a two-step path. A string that is already present is found
under a shared read lock, so the common warm-cache case — most identifiers
have been seen before — runs concurrently across threads with no exclusive
access. Only a genuinely new string escalates to the exclusive write lock,
and the insert re-checks for the string while holding it, so two threads
racing to intern the same new string still resolve to one symbol: the writer
that loses the race finds the winner’s entry instead of creating a duplicate.
The RwLock’s exclusivity is what makes “no duplicate symbols for the same
string” hold without a custom lock-free protocol.
Reads dominate once warm, so the RwLock is the right primitive here; writes
to new strings serialise. If write contention on a write-heavy workload ever
shows up in benchmarks, the storage can be sharded behind this same surface
without changing the API.
§Lock poisoning
If a thread panics while holding the lock it becomes poisoned. The interner’s own operations do not panic, so a poisoned lock means a panic originated elsewhere; rather than propagate that as a second panic, every method recovers the guard and continues. The stored data is structurally intact because interning is the only mutator and it does not unwind partway through under any input that fits in memory.
§Examples
use std::sync::Arc;
use std::thread;
use intern_lang::ConcurrentInterner;
let interner = Arc::new(ConcurrentInterner::new());
let mut handles = Vec::new();
for _ in 0..4 {
let interner = Arc::clone(&interner);
handles.push(thread::spawn(move || {
// Every thread interns the same names; they all agree on the symbols.
(interner.intern("loop"), interner.intern("break"))
}));
}
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let first = results[0];
assert!(results.iter().all(|&pair| pair == first));
assert_eq!(interner.len(), 2); // "loop" and "break", interned once eachImplementations§
Source§impl ConcurrentInterner
impl ConcurrentInterner
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates an empty concurrent interner. Like Interner::new, no allocation
happens until the first string is interned.
§Examples
use intern_lang::ConcurrentInterner;
let interner = ConcurrentInterner::new();
assert!(interner.is_empty());Sourcepub fn with_capacity(capacity: usize) -> Self
pub fn with_capacity(capacity: usize) -> Self
Creates an empty concurrent interner sized for about capacity distinct
strings before the dedup index grows. See Interner::with_capacity.
§Examples
use intern_lang::ConcurrentInterner;
let interner = ConcurrentInterner::with_capacity(4_096);
assert!(interner.is_empty());Sourcepub fn intern(&self, s: &str) -> Symbol
pub fn intern(&self, s: &str) -> Symbol
Interns s from a shared reference, returning its Symbol.
A string already present is resolved under a shared read lock; only a new string takes the exclusive write lock. The same string always yields the same symbol, even when several threads intern it at once.
§Examples
use intern_lang::ConcurrentInterner;
let interner = ConcurrentInterner::new();
let a = interner.intern("shared");
let b = interner.intern("shared");
assert_eq!(a, b);Sourcepub fn try_intern(&self, s: &str) -> Result<Symbol, InternError>
pub fn try_intern(&self, s: &str) -> Result<Symbol, InternError>
Interns s from a shared reference, returning its Symbol, or an error
if the symbol space is exhausted.
The fallible counterpart to intern, with the same
two-step locking. A string already present is returned under the read lock
and never errors; only a new string at the symbol-space bound returns
InternError::SymbolSpaceExhausted.
§Errors
Returns InternError::SymbolSpaceExhausted when s is new and the
interner has issued all of its symbols — unreachable for any input that
fits in memory.
§Examples
use intern_lang::ConcurrentInterner;
let interner = ConcurrentInterner::new();
let sym = interner.try_intern("name").expect("space available");
assert_eq!(interner.try_intern("name"), Ok(sym));Sourcepub fn get(&self, s: &str) -> Option<Symbol>
pub fn get(&self, s: &str) -> Option<Symbol>
Returns the symbol for s if it has already been interned, without
interning it.
§Examples
use intern_lang::ConcurrentInterner;
let interner = ConcurrentInterner::new();
let sym = interner.intern("present");
assert_eq!(interner.get("present"), Some(sym));
assert_eq!(interner.get("absent"), None);Sourcepub fn resolve_with<R, F>(&self, symbol: Symbol, f: F) -> Option<R>
pub fn resolve_with<R, F>(&self, symbol: Symbol, f: F) -> Option<R>
Runs f against the string symbol names, returning its result, or None
if symbol is out of range.
The read lock is held only for the duration of f, so the borrow stays
zero-copy without escaping the lock. Keep f short; it runs under the lock.
§Examples
use intern_lang::ConcurrentInterner;
let interner = ConcurrentInterner::new();
let sym = interner.intern("measured");
assert_eq!(interner.resolve_with(sym, str::len), Some(8));Sourcepub fn resolve(&self, symbol: Symbol) -> Option<String>
pub fn resolve(&self, symbol: Symbol) -> Option<String>
Resolves symbol to an owned String, or None if it is out of range.
This is the ergonomic counterpart to resolve_with:
it copies the bytes out so the result outlives the lock. On a hot path that
only inspects the string, prefer resolve_with to avoid the allocation.
§Examples
use intern_lang::ConcurrentInterner;
let interner = ConcurrentInterner::new();
let sym = interner.intern("owned");
assert_eq!(interner.resolve(sym).as_deref(), Some("owned"));Trait Implementations§
Source§impl Debug for ConcurrentInterner
impl Debug for ConcurrentInterner
Source§impl Default for ConcurrentInterner
impl Default for ConcurrentInterner
Source§impl Lookup for ConcurrentInterner
impl Lookup for ConcurrentInterner
Source§fn get(&self, s: &str) -> Option<Symbol>
fn get(&self, s: &str) -> Option<Symbol>
s if it has already been interned, without
interning it.