Skip to main content

cold_string/
lib.rs

1#![allow(rustdoc::bare_urls)]
2#![doc = include_str!("../README.md")]
3#![allow(unstable_name_collisions)]
4#![no_std]
5
6extern crate alloc;
7
8#[rustversion::before(1.84)]
9use sptr::Strict;
10
11use alloc::{
12    alloc::{alloc, dealloc, Layout},
13    borrow::{Cow, ToOwned},
14    boxed::Box,
15    str::Utf8Error,
16    string::String,
17};
18use core::{
19    cmp::Ordering,
20    fmt,
21    hash::{Hash, Hasher},
22    iter::FromIterator,
23    mem,
24    ops::Deref,
25    ptr,
26    ptr::NonNull,
27    slice, str,
28};
29
30mod vint;
31use crate::vint::VarInt;
32
33#[cfg(feature = "rkyv")]
34mod rkyv;
35
36const HEAP_ALIGN: usize = 4;
37const WIDTH: usize = mem::size_of::<usize>();
38
39/// Compact representation of immutable UTF-8 strings. Optimized for memory usage and struct packing.
40///
41/// # Example
42/// ```
43/// let s = cold_string::ColdString::new("qwerty");
44/// assert_eq!(s.as_str(), "qwerty");
45/// ```
46/// ```
47/// use core::mem::size_of;
48/// use cold_string::ColdString;
49///
50/// assert_eq!(size_of::<ColdString>(), size_of::<usize>());
51/// assert_eq!(size_of::<Option<ColdString>>(), size_of::<ColdString>());
52/// ```
53#[repr(transparent)]
54pub struct ColdString {
55    /// The first byte of `encoded` is the "tag" and it determines the type:
56    /// - 10xxxxxx: an encoded address for the heap. To decode, 10 is set to 00 and swapped
57    ///   with the LSB bits of the tag byte. The address is always a multiple of 4 (`HEAP_ALIGN`).
58    /// - 11111xxx: xxx is the length in range 0..=7, followed by length UTF-8 bytes.
59    /// - xxxxxxxx (valid UTF-8): 8 UTF-8 bytes.
60    /// The exception is if `encoded` is `usize::MAX`, the UTF-8 bytes are "\0\0\0\0\0\0\0\0".
61    encoded: NonNull<u8>,
62}
63
64static EIGHT_NUL: [u8; WIDTH] = [0u8; WIDTH];
65
66impl ColdString {
67    const TAG_MASK: usize = usize::from_ne_bytes(0b11000000usize.to_le_bytes());
68    const INLINE_TAG: usize = usize::from_ne_bytes(0b11111000usize.to_le_bytes());
69    const PTR_TAG: usize = usize::from_ne_bytes(0b10000000usize.to_le_bytes());
70    const LEN_MASK: usize = usize::from_ne_bytes(0b111usize.to_le_bytes());
71    const EIGHT_NUL_MAP: usize = usize::MAX;
72    const ROT: u32 = if cfg!(target_endian = "little") {
73        0
74    } else {
75        8 * (WIDTH - 1) as u32
76    };
77
78    /// Convert a slice of bytes into a [`ColdString`].
79    ///
80    /// A [`ColdString`] is a contiguous collection of bytes (`u8`s) that is valid [`UTF-8`](https://en.wikipedia.org/wiki/UTF-8).
81    /// This method converts from an arbitrary contiguous collection of bytes into a
82    /// [`ColdString`], failing if the provided bytes are not `UTF-8`.
83    ///
84    /// # Examples
85    /// ### Valid UTF-8
86    /// ```
87    /// # use cold_string::ColdString;
88    /// let bytes = [240, 159, 166, 128, 240, 159, 146, 175];
89    /// let compact = ColdString::from_utf8(&bytes).expect("valid UTF-8");
90    ///
91    /// assert_eq!(compact, "🦀💯");
92    /// ```
93    ///
94    /// ### Invalid UTF-8
95    /// ```
96    /// # use cold_string::ColdString;
97    /// let bytes = [255, 255, 255];
98    /// let result = ColdString::from_utf8(&bytes);
99    ///
100    /// assert!(result.is_err());
101    /// ```
102    pub fn from_utf8<B: AsRef<[u8]>>(v: B) -> Result<Self, Utf8Error> {
103        Ok(Self::new(str::from_utf8(v.as_ref())?))
104    }
105
106    /// Converts a vector of bytes to a [`ColdString`] without checking that the string contains
107    /// valid UTF-8.
108    ///
109    /// See the safe version, [`ColdString::from_utf8`], for more details.
110    ///
111    /// # Examples
112    ///
113    /// Basic usage:
114    ///
115    /// ```
116    /// # use cold_string::ColdString;
117    /// // some bytes, in a vector
118    /// let sparkle_heart = [240, 159, 146, 150];
119    ///
120    /// let sparkle_heart = unsafe {
121    ///     ColdString::from_utf8_unchecked(&sparkle_heart)
122    /// };
123    ///
124    /// assert_eq!("💖", sparkle_heart);
125    /// ```
126    pub unsafe fn from_utf8_unchecked<B: AsRef<[u8]>>(v: B) -> Self {
127        Self::new(str::from_utf8_unchecked(v.as_ref()))
128    }
129
130    /// Creates a new [`ColdString`] from any type that implements `AsRef<str>`.
131    /// If the string is shorter than `core::mem::size_of::<usize>()`, then it
132    /// will be inlined on the stack.
133    pub fn new<T: AsRef<str>>(x: T) -> Self {
134        let s = x.as_ref();
135        if s.len() <= WIDTH {
136            Self::new_inline(s)
137        } else {
138            Self::new_heap(s)
139        }
140    }
141
142    #[rustversion::attr(since(1.61), const)]
143    #[inline]
144    fn new_eight_nul() -> Self {
145        // SAFETY: PTR_TAG is non-zero
146        unsafe { Self::from_inline_buf(Self::EIGHT_NUL_MAP.to_ne_bytes()) }
147    }
148
149    #[inline]
150    fn is_eight_nul(&self) -> bool {
151        self.addr() == Self::EIGHT_NUL_MAP
152    }
153
154    #[inline]
155    const fn inline_buf(s: &str) -> [u8; WIDTH] {
156        debug_assert!(s.len() <= WIDTH);
157        let mut buf = [0u8; WIDTH];
158        if s.len() < WIDTH {
159            let tag =
160                (Self::INLINE_TAG | s.len().rotate_left(Self::ROT)).rotate_right(Self::ROT) as u8;
161            buf[0] = tag;
162        }
163        buf
164    }
165
166    /// SAFETY: b must not be all-zero
167    #[rustversion::attr(since(1.61), const)]
168    #[inline]
169    unsafe fn from_inline_buf(b: [u8; WIDTH]) -> Self {
170        let encoded = ptr::null_mut::<u8>().wrapping_add(usize::from_ne_bytes(b));
171        let encoded = NonNull::new_unchecked(encoded);
172        Self { encoded }
173    }
174
175    #[inline]
176    const fn utf8_start(l: usize) -> usize {
177        (l < WIDTH) as usize
178    }
179
180    #[inline]
181    fn new_inline(s: &str) -> Self {
182        if s.as_bytes() == EIGHT_NUL {
183            return Self::new_eight_nul();
184        }
185        let mut buf = Self::inline_buf(s);
186        let start = Self::utf8_start(s.len());
187        buf[start..s.len() + start].copy_from_slice(s.as_bytes());
188        // SAFETY:
189        // it is checked at the top of the function than s is not all NUL
190        // and the inline tag is not 0, so shorter strings will also be not all NUL
191        unsafe { Self::from_inline_buf(buf) }
192    }
193
194    /// Creates a new inline [`ColdString`] from `&'static str` at compile time.
195    ///
196    /// In a dynamic context you can use the method [`ColdString::new()`].
197    ///
198    /// # Panics
199    /// The string must be less than `core::mem::size_of::<usize>()`. Creating
200    /// a [`ColdString`] larger than that is not supported.
201    ///
202    ///
203    /// # Examples
204    /// ```
205    /// use cold_string::ColdString;
206    ///
207    /// const DEFAULT_NAME: ColdString = ColdString::new_inline_const("cold");
208    /// ```
209    #[rustversion::since(1.61)]
210    #[inline]
211    pub const fn new_inline_const(s: &str) -> Self {
212        if s.len() > WIDTH {
213            panic!(
214                "Length for `new_inline_const` must be less than `core::mem::size_of::<usize>()`."
215            );
216        }
217        if s.len() == WIDTH {
218            // can't do a slice comparison in const context
219            let bytes = unsafe { *(s.as_bytes() as *const _ as *const [u8; WIDTH]) };
220            let int = usize::from_ne_bytes(bytes);
221            if int == 0 {
222                return Self::new_eight_nul();
223            }
224        }
225        let mut buf = Self::inline_buf(s);
226        let start = Self::utf8_start(s.len());
227        let mut i = 0;
228        while i < s.len() {
229            buf[i + start] = s.as_bytes()[i];
230            i += 1;
231        }
232        // SAFETY:
233        // It is checked at the top of the function than s is not all NUL,
234        // and the inline tag is not 0, so shorter strings will also be not all NUL.
235        unsafe { Self::from_inline_buf(buf) }
236    }
237
238    #[rustversion::attr(since(1.71), const)]
239    #[inline]
240    fn ptr(&self) -> *const u8 {
241        self.encoded.as_ptr()
242    }
243
244    #[inline]
245    fn addr(&self) -> usize {
246        self.ptr().addr()
247    }
248
249    #[inline]
250    fn tag(&self) -> usize {
251        self.addr() & Self::TAG_MASK
252    }
253
254    /// Returns `true` if the string bytes are inlined.
255    #[inline]
256    pub fn is_inline(&self) -> bool {
257        self.tag() != Self::PTR_TAG
258    }
259
260    #[inline]
261    fn new_heap(s: &str) -> Self {
262        let len = s.len();
263        let (vint_len, len_buf) = VarInt::write(len as u64);
264        let total = vint_len + len;
265        let layout = Layout::from_size_align(total, HEAP_ALIGN).unwrap();
266
267        unsafe {
268            // SAFETY: the layout size is non-zero, since the smallest VarInt is one byte
269            let ptr = alloc(layout);
270            if ptr.is_null() {
271                alloc::alloc::handle_alloc_error(layout);
272            }
273
274            // TODO: can optimize this
275            ptr::copy_nonoverlapping(len_buf.as_ptr(), ptr, vint_len);
276            ptr::copy_nonoverlapping(s.as_ptr(), ptr.add(vint_len), len);
277            let encoded = ptr.map_addr(|addr| {
278                debug_assert!(addr % HEAP_ALIGN == 0);
279                let mut addr = addr.rotate_left(6 + Self::ROT);
280                addr |= Self::PTR_TAG;
281                addr
282            });
283            // SAFETY: encoded != 0 because Self::PTR_TAG != 0
284            let encoded = NonNull::new_unchecked(encoded);
285            Self { encoded }
286        }
287    }
288
289    #[inline]
290    fn heap_ptr(&self) -> *const u8 {
291        debug_assert!(!self.is_inline());
292        self.ptr().map_addr(|mut addr| {
293            addr ^= Self::PTR_TAG;
294            let addr = addr.rotate_right(6 + Self::ROT);
295            debug_assert!(addr % HEAP_ALIGN == 0);
296            addr
297        })
298    }
299
300    #[inline]
301    fn inline_len(&self) -> usize {
302        debug_assert!(!self.is_eight_nul());
303        let addr = self.addr();
304        match addr & Self::INLINE_TAG {
305            Self::INLINE_TAG => (addr & Self::LEN_MASK).rotate_right(Self::ROT),
306            _ => WIDTH,
307        }
308    }
309
310    /// Returns the length of this `ColdString`, in bytes, not [`char`]s or
311    /// graphemes. In other words, it might not be what a human considers the
312    /// length of the string.
313    ///
314    /// # Examples
315    ///
316    /// ```
317    /// use cold_string::ColdString;
318    ///
319    /// let a = ColdString::from("foo");
320    /// assert_eq!(a.len(), 3);
321    ///
322    /// let fancy_f = String::from("ƒoo");
323    /// assert_eq!(fancy_f.len(), 4);
324    /// assert_eq!(fancy_f.chars().count(), 3);
325    /// ```
326    #[inline]
327    pub fn len(&self) -> usize {
328        if self.is_eight_nul() {
329            return WIDTH;
330        } else if self.is_inline() {
331            self.inline_len()
332        } else {
333            unsafe {
334                let ptr = self.heap_ptr();
335                let (len, _) = VarInt::read(ptr);
336                len as usize
337            }
338        }
339    }
340
341    #[allow(unsafe_op_in_unsafe_fn)]
342    #[inline]
343    unsafe fn decode_inline(&self) -> &[u8] {
344        if self.is_eight_nul() {
345            return &EIGHT_NUL;
346        }
347        let len = self.inline_len();
348        // SAFETY: addr_of! avoids &self.ptr (which is UB due to alignment)
349        let self_bytes_ptr = ptr::addr_of!(self.encoded) as *const u8;
350        let start = Self::utf8_start(len);
351        slice::from_raw_parts(self_bytes_ptr.add(start), len)
352    }
353
354    #[allow(unsafe_op_in_unsafe_fn)]
355    #[inline]
356    unsafe fn decode_heap(&self) -> &[u8] {
357        let ptr = self.heap_ptr();
358        let (len, header) = VarInt::read(ptr);
359        let data = ptr.add(header);
360        slice::from_raw_parts(data, len)
361    }
362
363    /// Returns a byte slice of this `ColdString`'s contents.
364    ///
365    /// The inverse of this method is [`from_utf8`].
366    ///
367    /// [`from_utf8`]: String::from_utf8
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// let s = cold_string::ColdString::from("hello");
373    ///
374    /// assert_eq!(&[104, 101, 108, 108, 111], s.as_bytes());
375    /// ```
376    #[inline]
377    pub fn as_bytes(&self) -> &[u8] {
378        match self.is_inline() {
379            true => unsafe { self.decode_inline() },
380            false => unsafe { self.decode_heap() },
381        }
382    }
383
384    /// Returns a string slice containing the entire [`ColdString`].
385    ///
386    /// # Examples
387    /// ```
388    /// let s = cold_string::ColdString::new("hello");
389    ///
390    /// assert_eq!(s.as_str(), "hello");
391    /// ```
392    #[inline]
393    pub fn as_str(&self) -> &str {
394        unsafe { str::from_utf8_unchecked(self.as_bytes()) }
395    }
396
397    /// Returns `true` if this `ColdString` has a length of zero, and `false` otherwise.
398    ///
399    /// # Examples
400    ///
401    /// ```
402    /// let v = cold_string::ColdString::new("");
403    /// assert!(v.is_empty());
404    /// ```
405    #[inline]
406    pub fn is_empty(&self) -> bool {
407        self.len() == 0
408    }
409}
410
411impl Default for ColdString {
412    fn default() -> Self {
413        Self::new_inline("")
414    }
415}
416
417impl Deref for ColdString {
418    type Target = str;
419    fn deref(&self) -> &str {
420        self.as_str()
421    }
422}
423
424impl Drop for ColdString {
425    fn drop(&mut self) {
426        if !self.is_inline() {
427            let ptr = self.heap_ptr();
428            unsafe {
429                let (len, header) = VarInt::read(ptr);
430                let total = header + len;
431                let layout = Layout::from_size_align(total, HEAP_ALIGN).unwrap();
432                // SAFETY: if ptr is non-null then it was allocated by alloc() in new_heap()
433                dealloc(ptr as *mut u8, layout);
434            }
435        }
436    }
437}
438
439impl Clone for ColdString {
440    fn clone(&self) -> Self {
441        if self.is_inline() {
442            let ptr = self.ptr();
443            let encoded = unsafe { NonNull::new_unchecked(ptr as *mut _) };
444            Self { encoded }
445        } else {
446            Self::new_heap(self.as_str())
447        }
448    }
449}
450
451impl PartialEq for ColdString {
452    fn eq(&self, other: &Self) -> bool {
453        match (self.is_inline(), other.is_inline()) {
454            (true, true) => self.ptr() == other.ptr(),
455            (false, false) => unsafe { self.decode_heap() == other.decode_heap() },
456            _ => false,
457        }
458    }
459}
460
461impl Eq for ColdString {}
462
463impl Hash for ColdString {
464    fn hash<H: Hasher>(&self, state: &mut H) {
465        self.as_str().hash(state)
466    }
467}
468
469impl fmt::Debug for ColdString {
470    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
471        fmt::Debug::fmt(self.as_str(), f)
472    }
473}
474
475impl fmt::Display for ColdString {
476    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477        fmt::Display::fmt(self.as_str(), f)
478    }
479}
480
481impl From<&str> for ColdString {
482    fn from(s: &str) -> Self {
483        Self::new(s)
484    }
485}
486
487impl From<String> for ColdString {
488    fn from(s: String) -> Self {
489        Self::new(&s)
490    }
491}
492
493impl From<ColdString> for String {
494    fn from(s: ColdString) -> Self {
495        s.as_str().to_owned()
496    }
497}
498
499impl From<ColdString> for Cow<'_, str> {
500    #[inline]
501    fn from(s: ColdString) -> Self {
502        Self::Owned(s.into())
503    }
504}
505
506impl<'a> From<&'a ColdString> for Cow<'a, str> {
507    #[inline]
508    fn from(s: &'a ColdString) -> Self {
509        Self::Borrowed(s)
510    }
511}
512
513impl<'a> From<Cow<'a, str>> for ColdString {
514    fn from(cow: Cow<'a, str>) -> Self {
515        match cow {
516            Cow::Borrowed(s) => s.into(),
517            Cow::Owned(s) => s.into(),
518        }
519    }
520}
521
522impl From<Box<str>> for ColdString {
523    #[inline]
524    #[track_caller]
525    fn from(b: Box<str>) -> Self {
526        Self::new(&b)
527    }
528}
529
530impl FromIterator<char> for ColdString {
531    fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> Self {
532        let s: String = iter.into_iter().collect();
533        ColdString::new(&s)
534    }
535}
536
537unsafe impl Send for ColdString {}
538unsafe impl Sync for ColdString {}
539
540impl core::borrow::Borrow<str> for ColdString {
541    fn borrow(&self) -> &str {
542        self.as_str()
543    }
544}
545
546impl PartialEq<str> for ColdString {
547    fn eq(&self, other: &str) -> bool {
548        if self.is_inline() {
549            unsafe { self.decode_inline() == other.as_bytes() }
550        } else {
551            unsafe { self.decode_heap() == other.as_bytes() }
552        }
553    }
554}
555
556impl PartialEq<ColdString> for str {
557    fn eq(&self, other: &ColdString) -> bool {
558        other.eq(self)
559    }
560}
561
562impl PartialEq<&str> for ColdString {
563    fn eq(&self, other: &&str) -> bool {
564        self.eq(*other)
565    }
566}
567
568impl PartialEq<ColdString> for &str {
569    fn eq(&self, other: &ColdString) -> bool {
570        other.eq(*self)
571    }
572}
573
574impl AsRef<str> for ColdString {
575    #[inline]
576    fn as_ref(&self) -> &str {
577        self.as_str()
578    }
579}
580
581impl AsRef<[u8]> for ColdString {
582    #[inline]
583    fn as_ref(&self) -> &[u8] {
584        self.as_bytes()
585    }
586}
587
588impl Ord for ColdString {
589    fn cmp(&self, other: &Self) -> Ordering {
590        self.as_str().cmp(other.as_str())
591    }
592}
593
594impl PartialOrd for ColdString {
595    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
596        self.as_str().partial_cmp(other.as_str())
597    }
598}
599
600impl alloc::str::FromStr for ColdString {
601    type Err = core::convert::Infallible;
602    fn from_str(s: &str) -> Result<ColdString, Self::Err> {
603        Ok(ColdString::new(s))
604    }
605}
606
607#[cfg(feature = "serde")]
608impl serde::Serialize for ColdString {
609    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
610        serializer.serialize_str(self.as_str())
611    }
612}
613
614#[cfg(feature = "serde")]
615impl<'de> serde::Deserialize<'de> for ColdString {
616    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
617        let s = String::deserialize(d)?;
618        Ok(ColdString::new(&s))
619    }
620}
621
622#[cfg(all(test, feature = "serde"))]
623mod serde_tests {
624    use super::*;
625    use serde_test::{assert_tokens, Token};
626
627    #[test]
628    fn test_serde_cold_string_inline() {
629        let cs = ColdString::new("ferris");
630        assert_tokens(&cs, &[Token::Str("ferris")]);
631    }
632
633    #[test]
634    fn test_serde_cold_string_heap() {
635        let long_str = "This is a significantly longer string for heap testing";
636        let cs = ColdString::new(long_str);
637        assert_tokens(&cs, &[Token::Str(long_str)]);
638    }
639}
640
641#[cfg(test)]
642mod tests {
643    use super::*;
644    use core::hash::BuildHasher;
645    use hashbrown::hash_map::DefaultHashBuilder;
646
647    #[test]
648    fn test_layout() {
649        assert_eq!(mem::size_of::<ColdString>(), mem::size_of::<usize>());
650    }
651
652    #[test]
653    fn test_default() {
654        assert!(ColdString::default().is_empty());
655        assert_eq!(ColdString::default().len(), 0);
656        assert_eq!(ColdString::default(), "");
657        assert_eq!(ColdString::default(), ColdString::new(""));
658    }
659
660    fn assert_correct(s: &str) {
661        let cs = ColdString::new(s);
662        assert_eq!(s.len() <= mem::size_of::<usize>(), cs.is_inline());
663        assert_eq!(cs.len(), s.len());
664        assert_eq!(cs.as_bytes(), s.as_bytes());
665        assert_eq!(cs.as_str(), s);
666        assert_eq!(cs.clone(), cs);
667        let bh = DefaultHashBuilder::new();
668        let mut hasher1 = bh.build_hasher();
669        cs.hash(&mut hasher1);
670        let mut hasher2 = bh.build_hasher();
671        cs.clone().hash(&mut hasher2);
672        assert_eq!(hasher1.finish(), hasher2.finish());
673        assert_eq!(cs, s);
674        assert_eq!(s, cs);
675        assert_eq!(cs, *s);
676        assert_eq!(*s, cs);
677        let opt_s = Some(cs.clone());
678        assert_eq!(opt_s, Some(ColdString::new(s)));
679        assert!(opt_s != None);
680    }
681
682    #[test]
683    fn it_works() {
684        for s in [
685            "1",
686            "12",
687            "123",
688            "1234",
689            "12345",
690            "123456",
691            "1234567",
692            "12345678",
693            "123456789",
694            str::from_utf8(&[240, 159, 146, 150]).unwrap(),
695            "✅",
696            "❤️",
697            "🦀💯",
698            "🦀",
699            "💯",
700            "abcd",
701            "test",
702            "",
703            "\0",
704            "\0\0",
705            "\0\0\0",
706            "\0\0\0\0",
707            "\0\0\0\0\0\0\0",
708            "\0\0\0\0\0\0\0\0",
709            "1234567",
710            "12345678",
711            "longer test",
712            str::from_utf8(&[103, 39, 240, 145, 167, 156, 194, 165]).unwrap(),
713            "AaAa0 ® ",
714            str::from_utf8(&[240, 158, 186, 128, 240, 145, 143, 151]).unwrap(),
715        ] {
716            assert_correct(s);
717        }
718    }
719
720    fn char_from_leading_byte(b: u8) -> Option<char> {
721        match b {
722            0x00..=0x7F => Some(b as char),
723            0xC2..=0xDF => str::from_utf8(&[b, 0x91]).unwrap().chars().next(),
724            0xE0 => str::from_utf8(&[b, 0xA0, 0x91]).unwrap().chars().next(),
725            0xE1..=0xEC | 0xEE..=0xEF => str::from_utf8(&[b, 0x91, 0xA5]).unwrap().chars().next(),
726            0xED => str::from_utf8(&[b, 0x80, 0x91]).unwrap().chars().next(),
727            0xF0 => str::from_utf8(&[b, 0x90, 0x91, 0xA5])
728                .unwrap()
729                .chars()
730                .next(),
731            0xF1..=0xF3 => str::from_utf8(&[b, 0x91, 0xA5, 0x82])
732                .unwrap()
733                .chars()
734                .next(),
735            0xF4 => str::from_utf8(&[b, 0x80, 0x91, 0x82])
736                .unwrap()
737                .chars()
738                .next(),
739            _ => None,
740        }
741    }
742
743    #[test]
744    fn test_edges() {
745        let width = mem::size_of::<usize>();
746        for len in [width - 1, width, width + 1] {
747            for first_byte in 0u8..=255 {
748                let first_char = match char_from_leading_byte(first_byte) {
749                    Some(c) => c,
750                    None => continue,
751                };
752
753                let mut s = String::with_capacity(len);
754                s.push(first_char);
755
756                while s.len() < len {
757                    let c = core::char::from_digit((len - s.len()) as u32, 10).unwrap();
758                    s.push(c);
759                }
760
761                assert_correct(&s);
762            }
763        }
764    }
765
766    #[test]
767    fn test_unaligned_placement() {
768        for s_content in ["torture", "tor", "tortures", "tort", "torture torture"] {
769            let mut buffer = [0u8; 32];
770            for offset in 0..8 {
771                unsafe {
772                    let dst = buffer.as_mut_ptr().add(offset) as *mut ColdString;
773                    let s = ColdString::new(s_content);
774                    ptr::write_unaligned(dst, s);
775                    let recovered = ptr::read_unaligned(dst);
776                    assert_eq!(recovered.as_str(), s_content);
777                }
778            }
779        }
780    }
781
782    #[test]
783    fn ensure_zero_repr() {
784        assert!(str::from_utf8(&ColdString::EIGHT_NUL_MAP.to_ne_bytes()).is_err());
785    }
786
787    #[test]
788    fn test_const_8nul_vs_non_const() {
789        let nul8 = str::from_utf8(&EIGHT_NUL).unwrap();
790        let const8 = ColdString::new_inline_const(nul8);
791        let non_const = ColdString::new(nul8);
792        let cloned = non_const.clone();
793        assert_eq!(const8.ptr(), non_const.ptr());
794        assert_eq!(const8.ptr(), cloned.ptr());
795        // check that a null pointer will return a str pointing to EIGHT_NUL
796        assert_eq!(
797            &const8.as_str().as_bytes()[0] as *const u8,
798            (&EIGHT_NUL) as *const u8
799        );
800    }
801}