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 #[inline]
271 fn as_str(&self) -> &str {
272 match self.tag() {
273 DYNAMIC_TAG => unsafe {
274 let item = crate::dynamic::deref_from(self.unsafe_data);
275 from_utf8_unchecked(transmute::<&[u8], &'static [u8]>(&item.slice))
276 },
277 INLINE_TAG => {
278 let len = (self.unsafe_data.tag() & LEN_MASK) >> LEN_OFFSET;
279 let src = self.unsafe_data.data();
280 unsafe { std::str::from_utf8_unchecked(&src[..(len as usize)]) }
281 }
282 _ => unsafe { debug_unreachable!() },
283 }
284 }
285}
286
287#[cfg(test)]
288impl Atom {
289 pub(crate) fn ref_count(&self) -> usize {
290 match self.tag() {
291 DYNAMIC_TAG => {
292 let ptr = unsafe { crate::dynamic::deref_from(self.unsafe_data) };
293
294 triomphe::ThinArc::strong_count(&ptr.0)
295 }
296 _ => 1,
297 }
298 }
299}
300
301impl PartialEq for Atom {
302 #[inline]
303 fn eq(&self, other: &Self) -> bool {
304 let unsafe_data = self.unsafe_data;
305 let other_unsafe_data = other.unsafe_data;
306
307 if unsafe_data == other_unsafe_data {
308 return true;
309 }
310
311 let tag = unsafe_data.tag() & TAG_MASK;
312
313 if tag != (other_unsafe_data.tag() & TAG_MASK) {
314 return false;
315 }
316
317 match tag {
318 INLINE_TAG => false,
321 DYNAMIC_TAG => {
322 let this = unsafe { crate::dynamic::deref_from(unsafe_data) };
323 let other = unsafe { crate::dynamic::deref_from(other_unsafe_data) };
324
325 if this.header.header.hash != other.header.header.hash {
326 return false;
327 }
328
329 this.slice == other.slice
330 }
331 _ => unsafe { debug_unreachable!() },
332 }
333 }
334}
335
336impl Eq for Atom {}
337
338impl Hash for Atom {
339 #[inline(always)]
340 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
341 state.write_u64(self.get_hash());
342 }
343}
344
345impl Drop for Atom {
346 #[inline(always)]
347 fn drop(&mut self) {
348 if self.is_dynamic() {
349 unsafe { drop(crate::dynamic::restore_arc(self.unsafe_data)) }
350 }
351 }
352}
353
354impl Clone for Atom {
355 #[inline(always)]
356 fn clone(&self) -> Self {
357 Self::from_alias(self.unsafe_data)
358 }
359}
360
361impl_from_alias!(Atom);
362
363impl Deref for Atom {
364 type Target = str;
365
366 #[inline(always)]
367 fn deref(&self) -> &Self::Target {
368 self.as_str()
369 }
370}
371
372impl AsRef<str> for Atom {
373 #[inline(always)]
374 fn as_ref(&self) -> &str {
375 self.as_str()
376 }
377}
378
379impl PartialEq<str> for Atom {
380 #[inline]
381 fn eq(&self, other: &str) -> bool {
382 self.as_str() == other
383 }
384}
385
386impl PartialEq<&'_ str> for Atom {
387 #[inline]
388 fn eq(&self, other: &&str) -> bool {
389 self.as_str() == *other
390 }
391}
392
393impl PartialEq<Atom> for str {
394 #[inline]
395 fn eq(&self, other: &Atom) -> bool {
396 self == other.as_str()
397 }
398}
399
400impl Borrow<Wtf8Atom> for Atom {
401 #[inline(always)]
402 fn borrow(&self) -> &Wtf8Atom {
403 const _: () = assert!(std::mem::size_of::<Atom>() == std::mem::size_of::<Wtf8Atom>());
408 const _: () = assert!(std::mem::align_of::<Atom>() == std::mem::align_of::<Wtf8Atom>());
409 unsafe { transmute::<&Atom, &Wtf8Atom>(self) }
410 }
411}
412
413impl Atom {
414 pub unsafe fn from_wtf8_unchecked(s: Wtf8Atom) -> Self {
428 let s = ManuallyDrop::new(s);
429 Atom {
430 unsafe_data: s.unsafe_data,
431 }
432 }
433}
434
435#[cfg(test)]
436mod macro_tests {
437
438 use super::*;
439 #[test]
440 fn test_atom() {
441 assert_eq!(atom!(""), Atom::default());
443 assert_eq!(atom!(""), Atom::from(""));
444 assert_eq!(atom!("a"), Atom::from("a"));
445 assert_eq!(atom!("ab"), Atom::from("ab"));
446 assert_eq!(atom!("abc"), Atom::from("abc"));
447 assert_eq!(atom!("abcd"), Atom::from("abcd"));
448 assert_eq!(atom!("abcde"), Atom::from("abcde"));
449 assert_eq!(atom!("abcdef"), Atom::from("abcdef"));
450 assert_eq!(atom!("abcdefg"), Atom::from("abcdefg"));
451 assert_eq!(atom!("abcdefgh"), Atom::from("abcdefgh"));
452 assert_eq!(atom!("abcdefghi"), Atom::from("abcdefghi"));
453 }
454
455 #[test]
456 fn test_inline_atom() {
457 const STR: Atom = {
460 let inline = inline_atom("hello");
461 if inline.is_some() {
462 inline.unwrap()
463 } else {
464 unreachable!();
465 }
466 };
467 assert_eq!(STR, Atom::from("hello"));
468 }
469}