sinter/
istr.rs

1use {
2  ::core::{
3    borrow::Borrow,
4    convert::AsRef,
5    ffi::CStr,
6    fmt::{self, Debug, Display},
7    hash::Hash,
8    ops::Deref,
9  },
10  ::std::ffi::CString,
11};
12
13/// An Interned string
14#[derive(Eq, Copy, Clone, PartialOrd, Ord)]
15pub struct IStr(pub(super) &'static str);
16
17/// Create a collection of all the currently interned strings
18///
19/// The order of the items in the collection may not be stable.
20///
21/// ```rust
22/// # use sinter::{IStr, collect_interned_strings};
23/// let istrs: Vec<IStr> = collect_interned_strings();
24/// ```
25#[inline]
26pub fn collect_interned_strings<B>() -> B
27where
28  B: ::core::iter::FromIterator<IStr>,
29{
30  crate::interner::THE_INTERNER.collect_interned_strings()
31}
32
33// # constructors
34
35macro_rules! intern_doc {() => {
36r"Intern a new string, or return the extant [`IStr`] if one exists
37
38This operation may be slow, depending on whether the string has been previously
39interned."
40};}
41#[doc = intern_doc!()]
42#[inline]
43pub fn intern(s: &str) -> IStr {
44  crate::interner::THE_INTERNER.intern(s)
45}
46
47/// Locklessly find an extant [`IStr`] corresponding to the string given, if
48/// one exists
49///
50/// Call this to find out if a string has already been interned, without newly
51/// interning it if not.
52#[inline]
53pub fn get_interned(s: &str) -> Option<IStr> {
54  crate::interner::THE_INTERNER.get_interned(s)
55}
56
57impl IStr {
58  #[doc = intern_doc!()]
59  #[inline]
60  pub fn new(s: &str) -> Self {
61    intern(s)
62  }
63}
64
65impl From<&str> for IStr {
66  #[doc = intern_doc!()]
67  #[inline]
68  fn from(s: &str) -> Self {
69    intern(s)
70  }
71}
72
73impl From<String> for IStr {
74  #[doc = intern_doc!()]
75  #[inline]
76  fn from(s: String) -> Self {
77    intern(&s)
78  }
79}
80
81impl From<&String> for IStr {
82  #[doc = intern_doc!()]
83  #[inline]
84  fn from(s: &String) -> Self {
85    intern(s)
86  }
87}
88
89impl TryFrom<&CStr> for IStr {
90  type Error = ::core::str::Utf8Error;
91
92  #[doc = intern_doc!()]
93  #[inline]
94  fn try_from(c: &CStr) -> Result<Self, Self::Error> {
95    let s = c.to_str()?;
96    Ok(intern(s))
97  }
98}
99
100impl TryFrom<CString> for IStr {
101  type Error = ::core::str::Utf8Error;
102
103  #[doc = intern_doc!()]
104  #[inline]
105  fn try_from(c: CString) -> Result<Self, Self::Error> {
106    let s = c.to_str()?;
107    Ok(intern(s))
108  }
109}
110
111impl TryFrom<&CString> for IStr {
112  type Error = ::core::str::Utf8Error;
113
114  #[doc = intern_doc!()]
115  #[inline]
116  fn try_from(c: &CString) -> Result<Self, Self::Error> {
117    let s = c.to_str()?;
118    Ok(intern(s))
119  }
120}
121
122// # reference types & conversion
123
124impl Deref for IStr {
125  type Target = str;
126
127  #[inline]
128  fn deref(&self) -> &str {
129    self.0
130  }
131}
132
133impl AsRef<str> for IStr {
134  #[inline]
135  fn as_ref(&self) -> &str {
136    self.0
137  }
138}
139
140impl AsRef<CStr> for IStr {
141  #[inline]
142  fn as_ref(&self) -> &CStr {
143    self.as_c_str()
144  }
145}
146
147impl Borrow<str> for IStr {
148  #[inline]
149  fn borrow(&self) -> &'static str {
150    self.0
151  }
152}
153
154impl IStr {
155  /// get the underlying `&str`
156  #[inline]
157  pub fn as_str(&self) -> &'static str {
158    self.0
159  }
160
161  /// zero-cost conversion to a null terminated [`CStr`]
162  #[inline]
163  pub fn as_c_str(&self) -> &'static CStr {
164    let ptr = self.0.as_ptr();
165    // safety: The Interner always leaves a trailing null byte
166    unsafe { CStr::from_ptr(ptr as _) }
167  }
168}
169
170impl From<IStr> for &'static str {
171  #[inline]
172  fn from(i: IStr) -> &'static str {
173    i.as_str()
174  }
175}
176
177impl From<IStr> for &'static CStr {
178  #[inline]
179  fn from(i: IStr) -> &'static CStr {
180    i.as_c_str()
181  }
182}
183
184impl From<IStr> for String {
185  #[inline]
186  fn from(s: IStr) -> String {
187    s.0.to_owned()
188  }
189}
190
191impl From<IStr> for CString {
192  #[inline]
193  fn from(s: IStr) -> CString {
194    s.as_c_str().to_owned()
195  }
196}
197
198impl IStr {
199  /// Create a new [`CString`] from the value
200  #[inline]
201  pub fn to_c_string(&self) -> CString {
202    self.as_c_str().to_owned()
203  }
204}
205
206// note: `Display` gives us `to_string()` by way of `ToString`
207impl Display for IStr {
208  #[inline]
209  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210    f.write_str(self.0)
211  }
212}
213
214impl Debug for IStr {
215  #[inline]
216  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217    f.write_fmt(format_args!("IStr(\"{}\")", self.0))
218  }
219}
220
221// # equality
222
223// istr
224impl PartialEq for IStr {
225  /// fast [`IStr`] comparison (pointer equality test)
226  #[inline]
227  fn eq(&self, rhs: &IStr) -> bool {
228    // it is sufficient to compare the pointers, because the Interner never
229    // produces two distinct [`IStr`]s with the same data, and does not alias
230    // strings in the pool.
231    ::core::ptr::eq(self.0.as_ptr(), rhs.0.as_ptr())
232  }
233}
234
235// str
236impl PartialEq<&str> for IStr {
237  /// full (potentially slow) string comparison
238  #[inline]
239  fn eq(&self, other: &&str) -> bool {
240    self.0 == *other
241  }
242}
243impl PartialEq<IStr> for &str {
244  /// full (potentially slow) string comparison
245  #[inline]
246  fn eq(&self, other: &IStr) -> bool {
247    *self == other.0
248  }
249}
250
251// cstr
252impl PartialEq<&CStr> for IStr {
253  /// full (potentially slow) string comparison
254  #[inline]
255  fn eq(&self, other: &&CStr) -> bool {
256    self.as_c_str() == *other
257  }
258}
259impl PartialEq<IStr> for &CStr {
260  /// full (potentially slow) string comparison
261  #[inline]
262  fn eq(&self, other: &IStr) -> bool {
263    *self == other.as_c_str()
264  }
265}
266
267// string
268impl PartialEq<String> for IStr {
269  /// full (potentially slow) string comparison
270  #[inline]
271  fn eq(&self, other: &String) -> bool {
272    self.0 == other
273  }
274}
275impl PartialEq<IStr> for String {
276  /// full (potentially slow) string comparison
277  #[inline]
278  fn eq(&self, other: &IStr) -> bool {
279    self == other.0
280  }
281}
282impl PartialEq<&String> for IStr {
283  /// full (potentially slow) string comparison
284  #[inline]
285  fn eq(&self, other: &&String) -> bool {
286    self.0 == *other
287  }
288}
289impl PartialEq<IStr> for &String {
290  /// full (potentially slow) string comparison
291  #[inline]
292  fn eq(&self, other: &IStr) -> bool {
293    *self == other.0
294  }
295}
296
297// cstring
298impl PartialEq<CString> for IStr {
299  /// full (potentially slow) string comparison
300  #[inline]
301  fn eq(&self, other: &CString) -> bool {
302    let o: &CStr = other;
303    self.as_c_str() == o
304  }
305}
306impl PartialEq<IStr> for CString {
307  /// full (potentially slow) string comparison
308  #[inline]
309  fn eq(&self, other: &IStr) -> bool {
310    let s: &CStr = self;
311    s == other.as_c_str()
312  }
313}
314impl PartialEq<&CString> for IStr {
315  /// full (potentially slow) string comparison
316  #[inline]
317  fn eq(&self, other: &&CString) -> bool {
318    let o: &CStr = other;
319    self.as_c_str() == o
320  }
321}
322impl PartialEq<IStr> for &CString {
323  /// full (potentially slow) string comparison
324  #[inline]
325  fn eq(&self, other: &IStr) -> bool {
326    let s: &CStr = self;
327    s == other.as_c_str()
328  }
329}
330
331// # hashing
332
333impl Hash for IStr {
334  /// This feeds the underlying &str into the hasher
335  #[inline]
336  fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
337    self.as_str().hash(state);
338  }
339}
340
341impl IStr {
342  /// The [wyhash](https://crates.io/crates/wyhash) value of this string
343  ///
344  /// This value is cached next to the string by the interner so this method
345  /// call is free.
346  ///
347  /// wyhash is a very cheap non-cryptographic hash function, useful when using
348  /// [`IStr`] as a key in a [`hashbrown::HashTable`].
349  #[inline]
350  pub fn wyhash(&self) -> u64 {
351    use crate::interner::SIZE_OF_WYHASH;
352    // safety: the Interner caches the u64 wyhash in the 8 bytes preceding the
353    // string data
354    let hash_array: &[u8; SIZE_OF_WYHASH] = unsafe {
355      let hash_ptr = self.0.as_ptr().sub(SIZE_OF_WYHASH);
356      &*(hash_ptr as *const [u8; SIZE_OF_WYHASH])
357    };
358    u64::from_ne_bytes(*hash_array)
359  }
360}