intern_lang/lookup.rs
1//! The [`Lookup`] trait: the read-side contract shared by every interner.
2
3use crate::symbol::Symbol;
4
5/// The read-side contract every interner in the crate implements.
6///
7/// `Lookup` is the seam that lets generic code accept either the single-threaded
8/// [`Interner`](crate::Interner) or the
9/// [`ConcurrentInterner`](crate::ConcurrentInterner): both look a string up to its
10/// symbol, resolve a symbol back to its bytes, and report their size — all through
11/// `&self`. Interning is deliberately *not* on this trait, because the two
12/// interners disagree on how it is called (`&mut self` for the single-threaded
13/// one, `&self` for the concurrent one), and forcing a common signature would tax
14/// the single-threaded hot path with synchronisation it does not need.
15///
16/// Resolution is expressed as [`resolve_with`](Lookup::resolve_with) — a closure
17/// that runs against the borrowed string — rather than a method returning
18/// `&str`. A concurrent interner can only expose the bytes while it holds its
19/// read lock, so the borrow cannot outlive the call; a closure keeps resolution
20/// zero-copy for both interners without leaking the lock's lifetime into the
21/// signature.
22///
23/// # Examples
24///
25/// ```
26/// use intern_lang::{Interner, Lookup};
27///
28/// fn name_len(interner: &impl Lookup, s: &str) -> Option<usize> {
29/// let sym = interner.get(s)?;
30/// interner.resolve_with(sym, str::len)
31/// }
32///
33/// let mut interner = Interner::new();
34/// let _ = interner.intern("identifier");
35/// assert_eq!(name_len(&interner, "identifier"), Some(10));
36/// assert_eq!(name_len(&interner, "missing"), None);
37/// ```
38pub trait Lookup {
39 /// Returns the symbol for `s` if it has already been interned, without
40 /// interning it.
41 fn get(&self, s: &str) -> Option<Symbol>;
42
43 /// Runs `f` against the string `symbol` names, returning its result, or `None`
44 /// if `symbol` is out of range for this interner.
45 ///
46 /// The borrow passed to `f` is valid only for the duration of the call. This
47 /// keeps resolution zero-copy across both the single-threaded and concurrent
48 /// interners; the concurrent one holds its read lock for exactly the span of
49 /// `f`.
50 fn resolve_with<R, F>(&self, symbol: Symbol, f: F) -> Option<R>
51 where
52 F: FnOnce(&str) -> R;
53
54 /// Returns the number of distinct strings interned so far.
55 fn len(&self) -> usize;
56
57 /// Returns `true` if no strings have been interned.
58 fn is_empty(&self) -> bool {
59 self.len() == 0
60 }
61}