1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Compact string interning with local and concurrent engines.
//!
//! String interning is a common technique to reduce memory use and improve
//! performance when code handles the same strings over and over
//! (identifiers in a compiler, tags/labels in telemetry, keys in a parser).
//! The benefits of interning include:
//!
//! * Strings are stored once and reused which saves memory and CPU cycles
//!
//! * Strings are referenced with a 4-byte handle instead of an 8- or 16-byte reference.
//! This can save considerable memory.
//!
//! * Hashing and comparison of interned strings is faster since it doesn't require
//! hashing or comparing whole strings, merely their 4-byte handle.
//!
//! To intern a string, you supply it to the interning engine and it hands back a handle.
//! No matter how many times you try to intern a given string, it gets deduplicated and
//! gets added only once to the data store, and you get back the same handle. Later, you can
//! use the handle to retrieve the actual string.
//!
//! # Handles
//!
//! Interning yields a [`Sym`] — a 4-byte, `Copy` handle. It's cheap to store and
//! pass, `Option<Sym>` is also 4 bytes, and within one interner
//! equal strings always produce equal handles, so `==` on handles is an O(1) stand
//! in for string equality and a `Sym` works directly as a `HashMap` key.
//!
//! # Choosing an interner
//!
//! `internity` supports two different string interners for different scenarios:
//!
//! * [`LocalLexicon`]. This engine interns strings from one thread and avoids
//! synchronization during the fill phase. Shared readers can resolve strings
//! concurrently.
//!
//! * [`ThreadedLexicon`]. This engine allows multiple threads to intern words
//! concurrently and uses synchronization to coordinate inserts.
//!
//! Both engines can be used through the [`Lexicon`] trait, allowing generic code
//! to intern strings without selecting a concrete engine.
//!
//! # The intern → freeze → read pattern
//!
//! Interning and resolving have different needs, so the typical lifecycle is to
//! intern during a build phase, then [`freeze`](LocalLexicon::freeze) into a
//! [`Reader`] for the read phase. A `Reader` is immutable, `Send + Sync`, and its
//! lookups are lock-free — ideal for sharing across threads.
//!
//! ```
//! use internity::{LocalLexicon, Reader};
//!
//! // Build phase.
//! let mut lexicon = LocalLexicon::new();
//! let hello = lexicon.intern("hello");
//! let world = lexicon.intern("world");
//! assert_eq!(lexicon.intern("hello"), hello); // deduplicated
//!
//! // Read phase: freeze once, then resolve (here you could share `reader`
//! // across threads).
//! let reader = lexicon.freeze();
//! assert_eq!(reader.resolve(hello), "hello");
//! assert_eq!(reader.resolve(world), "world");
//! ```
//!
//! # Custom hashers
//!
//! Both interners default to a fast, non-cryptographic hasher and are generic over
//! the [`BuildHasher`](core::hash::BuildHasher), like
//! `HashMap`. Use `with_hasher` to supply your own — for
//! example a DoS-resistant hasher when interning untrusted input.
//!
//! # Production guidance
//!
//! * A [`Sym`] is local to the interner that created it. A foreign handle is
//! range-checked, but an in-range numeric value can resolve to an unrelated
//! string. Persist or transmit handles together with the matching interner.
//! * The default Fx hasher is fast but not collision-attack resistant. Supply a
//! defensive `BuildHasher` when strings can be selected by an attacker.
//! * Interners do not remove individual strings. Memory grows during the fill
//! phase until the interner is dropped or frozen.
//! * A [`Sym`] does not implement [`serde::Serialize`]/`Deserialize` on its own:
//! a bare handle is a meaningless integer without its interner. Serialize
//! handles with the reader-aware [`se::SerializeIn`] derive (which resolves
//! each handle to its string) and read them back with the [`de::DeserializeIn`]
//! derive, so a value round-trips through a self-describing encoding. Serialize
//! a whole corpus by freezing the interner and wrapping the [`Reader`] in
//! [`se::SerializeReader`].
//! * Exceeding the documented byte or handle limits panics. Applications that
//! accept untrusted strings should enforce count and byte quotas before
//! interning.
//!
//! # Capacity
//!
//! A single [`LocalLexicon`] holds up to approximately 4 GiB of string bytes; a
//! [`ThreadedLexicon`] up to approximately 256 GiB (across its shards). Either way
//! the number of distinct strings is bounded by the 4-byte handle (approximately
//! 4.29 billion). Exceeding these limits panics rather than corrupting data.
//!
//! # Cargo features
//!
//! * `std` *(default)* — enables the concurrent [`ThreadedLexicon`]. Without it the
//! crate is `no_std` + `alloc`: [`LocalLexicon`], [`Lexicon`], [`Sym`], and
//! [`Reader`] still work.
//! * `serde` — reader-aware serialization: the [`se::SerializeIn`] /
//! [`de::DeserializeIn`] derives, [`se::SerializeReader`] for a whole corpus,
//! and `DeserializeIn` on the interners. [`ThreadedLexicon`] deserialization
//! requires its default hasher so deserialization can reproduce identical
//! handles.
extern crate alloc;
extern crate std;
// Unchecked UTF-8 reconstruction is isolated in `storage`; reader modules forbid
// unsafe code.
pub use Lexicon;
pub use LocalLexicon;
pub use Reader;
pub use Sym;
pub use ;
pub use ;
pub use ThreadedLexicon;