1#![cfg_attr(feature = "atom_size_128", feature(integer_atomics))]
2use core::str;
5use std::{
6 borrow::Borrow,
7 fmt::{Debug, Display},
8 hash::Hash,
9 mem::{forget, transmute, ManuallyDrop},
10 num::NonZeroU8,
11 ops::Deref,
12 str::from_utf8_unchecked,
13};
14
15use debug_unreachable::debug_unreachable;
16use once_cell::sync::Lazy;
17
18pub use crate::dynamic::{global_atom_store_gc, AtomStore};
19use crate::{
20 macros::{get_hash, impl_from_alias},
21 tagged_value::TaggedValue,
22};
23
24mod dynamic;
25mod global_store;
26mod macros;
27mod tagged_value;
28#[cfg(test)]
29mod tests;
30pub mod wtf8;
31mod wtf8_atom;
32
33pub use wtf8_atom::Wtf8Atom;
34
35#[repr(transparent)]
109pub struct Atom {
110 unsafe_data: TaggedValue,
112}
113
114#[doc(hidden)]
115pub type CachedAtom = Lazy<Atom>;
116
117#[doc(hidden)]
118pub const fn inline_atom(s: &str) -> Option<Atom> {
119 dynamic::inline_atom(s)
120}
121
122#[macro_export]
124macro_rules! atom {
125 ($s:expr) => {{
126 const INLINE: ::core::option::Option<$crate::Atom> = $crate::inline_atom($s);
127 if INLINE.is_some() {
130 INLINE.unwrap()
131 } else {
132 #[inline(never)]
134 fn get_atom() -> $crate::Atom {
135 static CACHE: $crate::CachedAtom =
136 $crate::CachedAtom::new(|| $crate::Atom::from($s));
137
138 (*CACHE).clone()
139 }
140
141 get_atom()
142 }
143 }};
144}
145
146impl Default for Atom {
147 #[inline(never)]
148 fn default() -> Self {
149 atom!("")
150 }
151}
152
153unsafe impl Send for Atom {}
155
156unsafe impl Sync for Atom {}
158
159impl Display for Atom {
160 #[inline]
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 Display::fmt(self.as_str(), f)
163 }
164}
165
166impl Debug for Atom {
167 #[inline]
168 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 Debug::fmt(self.as_str(), f)
170 }
171}
172
173#[cfg(feature = "serde")]
174impl serde::ser::Serialize for Atom {
175 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
176 where
177 S: serde::ser::Serializer,
178 {
179 serializer.serialize_str(self)
180 }
181}
182
183#[cfg(feature = "serde")]
184impl<'de> serde::de::Deserialize<'de> for Atom {
185 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
186 where
187 D: serde::Deserializer<'de>,
188 {
189 String::deserialize(deserializer).map(Self::new)
190 }
191}
192const DYNAMIC_TAG: u8 = 0b_00;
193const INLINE_TAG: u8 = 0b_01; const INLINE_TAG_INIT: NonZeroU8 = unsafe { NonZeroU8::new_unchecked(INLINE_TAG) };
195const TAG_MASK: u8 = 0b_11;
196const LEN_OFFSET: usize = 4;
197const LEN_MASK: u8 = 0xf0;
198
199impl Atom {
202 #[inline(always)]
203 pub fn new<S>(s: S) -> Self
204 where
205 Self: From<S>,
206 {
207 Self::from(s)
208 }
209
210 #[inline(always)]
211 fn tag(&self) -> u8 {
212 self.unsafe_data.tag() & TAG_MASK
213 }
214
215 #[inline(always)]
217 fn is_dynamic(&self) -> bool {
218 self.tag() == DYNAMIC_TAG
219 }
220}
221
222impl Atom {
223 fn from_mutated_str<F: FnOnce(&mut str)>(s: &str, f: F) -> Self {
224 let mut buffer = [0u8; 64];
225
226 if let Some(buffer_prefix) = buffer.get_mut(..s.len()) {
227 buffer_prefix.copy_from_slice(s.as_bytes());
228 let as_str = unsafe { ::std::str::from_utf8_unchecked_mut(buffer_prefix) };
231 f(as_str);
232 Atom::from(&*as_str)
233 } else {
234 let mut string = s.to_owned();
235 f(&mut string);
236 Atom::from(string)
237 }
238 }
239
240 pub fn to_ascii_uppercase(&self) -> Self {
244 for (i, b) in self.bytes().enumerate() {
245 if let b'a'..=b'z' = b {
246 return Atom::from_mutated_str(self, |s| s[i..].make_ascii_uppercase());
247 }
248 }
249 self.clone()
250 }
251
252 pub fn to_ascii_lowercase(&self) -> Self {
256 for (i, b) in self.bytes().enumerate() {
257 if let b'A'..=b'Z' = b {
258 return Atom::from_mutated_str(self, |s| s[i..].make_ascii_lowercase());
259 }
260 }
261 self.clone()
262 }
263}
264
265impl Atom {
266 fn get_hash(&self) -> u64 {
267 get_hash!(self)
268 }
269
270 fn as_str(&self) -> &str {
271 match self.tag() {
272 DYNAMIC_TAG => unsafe {
273 let item = crate::dynamic::deref_from(self.unsafe_data);
274 from_utf8_unchecked(transmute::<&[u8], &'static [u8]>(&item.slice))
275 },
276 INLINE_TAG => {
277 let len = (self.unsafe_data.tag() & LEN_MASK) >> LEN_OFFSET;
278 let src = self.unsafe_data.data();
279 unsafe { std::str::from_utf8_unchecked(&src[..(len as usize)]) }
280 }
281 _ => unsafe { debug_unreachable!() },
282 }
283 }
284}
285
286#[cfg(test)]
287impl Atom {
288 pub(crate) fn ref_count(&self) -> usize {
289 match self.tag() {
290 DYNAMIC_TAG => {
291 let ptr = unsafe { crate::dynamic::deref_from(self.unsafe_data) };
292
293 triomphe::ThinArc::strong_count(&ptr.0)
294 }
295 _ => 1,
296 }
297 }
298}
299
300impl PartialEq for Atom {
301 #[inline]
302 fn eq(&self, other: &Self) -> bool {
303 let unsafe_data = self.unsafe_data;
304 let other_unsafe_data = other.unsafe_data;
305
306 if unsafe_data == other_unsafe_data {
307 return true;
308 }
309
310 let tag = unsafe_data.tag() & TAG_MASK;
311
312 if tag != (other_unsafe_data.tag() & TAG_MASK) {
313 return false;
314 }
315
316 match tag {
317 INLINE_TAG => false,
320 DYNAMIC_TAG => {
321 let this = unsafe { crate::dynamic::deref_from(unsafe_data) };
322 let other = unsafe { crate::dynamic::deref_from(other_unsafe_data) };
323
324 if this.header.header.hash != other.header.header.hash {
325 return false;
326 }
327
328 this.slice == other.slice
329 }
330 _ => unsafe { debug_unreachable!() },
331 }
332 }
333}
334
335impl Eq for Atom {}
336
337impl Hash for Atom {
338 #[inline(always)]
339 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
340 state.write_u64(self.get_hash());
341 }
342}
343
344impl Drop for Atom {
345 #[inline(always)]
346 fn drop(&mut self) {
347 if self.is_dynamic() {
348 unsafe { drop(crate::dynamic::restore_arc(self.unsafe_data)) }
349 }
350 }
351}
352
353impl Clone for Atom {
354 #[inline(always)]
355 fn clone(&self) -> Self {
356 Self::from_alias(self.unsafe_data)
357 }
358}
359
360impl_from_alias!(Atom);
361
362impl Deref for Atom {
363 type Target = str;
364
365 #[inline(always)]
366 fn deref(&self) -> &Self::Target {
367 self.as_str()
368 }
369}
370
371impl AsRef<str> for Atom {
372 #[inline(always)]
373 fn as_ref(&self) -> &str {
374 self.as_str()
375 }
376}
377
378impl PartialEq<str> for Atom {
379 #[inline]
380 fn eq(&self, other: &str) -> bool {
381 self.as_str() == other
382 }
383}
384
385impl PartialEq<&'_ str> for Atom {
386 #[inline]
387 fn eq(&self, other: &&str) -> bool {
388 self.as_str() == *other
389 }
390}
391
392impl PartialEq<Atom> for str {
393 #[inline]
394 fn eq(&self, other: &Atom) -> bool {
395 self == other.as_str()
396 }
397}
398
399impl Borrow<Wtf8Atom> for Atom {
400 #[inline(always)]
401 fn borrow(&self) -> &Wtf8Atom {
402 const _: () = assert!(std::mem::size_of::<Atom>() == std::mem::size_of::<Wtf8Atom>());
407 const _: () = assert!(std::mem::align_of::<Atom>() == std::mem::align_of::<Wtf8Atom>());
408 unsafe { transmute::<&Atom, &Wtf8Atom>(self) }
409 }
410}
411
412impl Atom {
413 pub unsafe fn from_wtf8_unchecked(s: Wtf8Atom) -> Self {
427 let s = ManuallyDrop::new(s);
428 Atom {
429 unsafe_data: s.unsafe_data,
430 }
431 }
432}
433
434#[cfg(test)]
435mod macro_tests {
436
437 use super::*;
438 #[test]
439 fn test_atom() {
440 assert_eq!(atom!(""), Atom::default());
442 assert_eq!(atom!(""), Atom::from(""));
443 assert_eq!(atom!("a"), Atom::from("a"));
444 assert_eq!(atom!("ab"), Atom::from("ab"));
445 assert_eq!(atom!("abc"), Atom::from("abc"));
446 assert_eq!(atom!("abcd"), Atom::from("abcd"));
447 assert_eq!(atom!("abcde"), Atom::from("abcde"));
448 assert_eq!(atom!("abcdef"), Atom::from("abcdef"));
449 assert_eq!(atom!("abcdefg"), Atom::from("abcdefg"));
450 assert_eq!(atom!("abcdefgh"), Atom::from("abcdefgh"));
451 assert_eq!(atom!("abcdefghi"), Atom::from("abcdefghi"));
452 }
453
454 #[test]
455 fn test_inline_atom() {
456 const STR: Atom = {
459 let inline = inline_atom("hello");
460 if inline.is_some() {
461 inline.unwrap()
462 } else {
463 unreachable!();
464 }
465 };
466 assert_eq!(STR, Atom::from("hello"));
467 }
468}