german_str/
lib.rs

1#![no_std]
2#![cfg(target_pointer_width = "64")]
3
4extern crate alloc;
5
6use alloc::borrow::{Cow, ToOwned as _};
7use alloc::boxed::Box;
8use alloc::slice;
9use alloc::string::String;
10use alloc::sync::Arc;
11use core::{cmp, fmt, ptr};
12use core::alloc::Layout;
13use core::borrow::Borrow;
14use core::ops::Deref;
15use core::ptr::NonNull;
16use core::str::FromStr;
17
18/// The maximum number of chars a GermanStr can contain before requiring
19/// a heap allocation.
20pub const MAX_INLINE_BYTES: usize = 12;
21
22/// The absolute maximum number of chars a GermanStr can hold.
23/// Since the len is an u32, it is 2^32.
24pub const MAX_LEN: usize = 2_usize.pow(32);
25
26/// Stored in stolen bits of the heap pointer, to indicate that it is an
27/// owned pointer and its heap allocation should be freed on drop.
28const OWNED_PTR: usize = 0;
29
30/// Stored in the stolen bits of the heap pointer, to indicate that it is a
31/// shared buffer and that the user is responsible for freeing it.
32const SHARED_PTR: usize = usize::MAX;
33
34/// A string type with the following properties:
35///
36/// * Immutable.
37/// * `size_of::<GermanStr>() == 16`
38/// * Strings of 12 or less bytes are entirely located on the stack.
39/// * Fast comparisons.
40#[repr(C)]
41pub struct GermanStr {
42    /// Number of chars of the string.
43    /// Serves as a tag for the variant used by the `last8` field, based on
44    /// whether it is longer than `MAX_INLINE_BYTES` or not.
45    len: u32,
46
47    /// The first 4 bytes of the string. If it is shorter than 4 bytes, extra
48    /// bytes are set to 0.
49    ///
50    /// Since an UTF-8 char can consist of 1-4 bytes, this field can store
51    /// 1-4 chars, and potentially only part of the last char.
52    /// In every case, this array can still be used to speed up comparisons
53    /// because UTF-8 strings are ordered byte-wise.
54    prefix: [u8; 4],
55
56    /// If the string is longer than 12 bytes, is a pointer to
57    /// the chars on the heap.
58    /// By default, this pointer is unique and has ownership of the allocation,
59    /// but the heap buffer can be shared if `leaky_shared_clone` is called,
60    /// in which case you are then responsible for freeing it correctly.
61    /// The prefix is also included in the buffer.
62    ///
63    /// If the string fits in 12 bytes, is an `[u8; 8]`, with extra bytes
64    /// set to 0 (the first 4 bytes being stored in `self.prefix`).
65    last8: Last8,
66}
67
68#[derive(Copy, Clone)]
69/// Holds the last 8 bytes of a `GermanStr`.
70union Last8 {
71    /// Non-null pointer to u8 with 1 bit of virtual address space stolen.
72    ptr: ointers::NotNull<u8, 0, false, 1>,
73    // Safety:
74    // "If compiling for a 64bit arch, V must be at most 25": we have
75    // #![cfg(target_pointer_width = "64")] and V == 1.
76
77    /// If the string is shorter than 12 bytes, extra bytes are set to 0.
78    buf: [u8; 8],
79}
80
81#[derive(Debug, Clone, Copy)]
82/// Represents the reasons why creating a new `GermanStr` could fail.
83pub enum InitError {
84    /// `GermanStr`s use an u32 to store their length, hence they can't contain
85    /// more than 2^32 bytes (~4GB).
86    TooLong,
87}
88
89impl GermanStr {
90    #[inline]
91    /// Main function to create a GermanStr.
92    pub fn new(src: impl AsRef<str>) -> Result<Self, InitError> {
93        let src = src.as_ref();
94        if src.len() > MAX_LEN {
95            return Err(InitError::TooLong);
96        }
97        if src.len() <= MAX_INLINE_BYTES {
98            return Ok(GermanStr::new_inline(src));
99        }
100
101        let layout = Layout::array::<u8>(src.len())
102            .map_err(|_| InitError::TooLong)?;
103        let ptr = unsafe {
104            // Safety: layout is not zero-sized (src.len() <= MAX_INLINE_BYTES guard).
105            alloc::alloc::alloc(layout)
106        };
107        let Some(ptr) = NonNull::new(ptr) else {
108            alloc::alloc::handle_alloc_error(layout);
109        };
110        unsafe {
111            // Safety:
112            //   1. We assume src is a valid object.
113            //   2. ptr is valid: it was checked for null and allocated
114            //      for src.len() bytes.
115            //   3. *_ u8 is always aligned.
116            //   4. The 2 regions can't overlap since they belong to different objects.
117            ptr::copy_nonoverlapping(
118                src.as_bytes().as_ptr(),
119                ptr.as_ptr(),
120                src.len(),
121            );
122        }
123        let ointer = unsafe {
124            // Safety: see Last8.ptr declaration.
125            ointers::NotNull::new_stealing(ptr, OWNED_PTR)
126        };
127        Ok(GermanStr {
128            len: src.len() as u32,
129            prefix: str_prefix::<&str>(&src),
130            last8: Last8 { ptr: ointer },
131        })
132    }
133
134    #[inline]
135    /// Attempts to create a GermanStr entirely stored in the struct itself,
136    /// without heap allocations.
137    ///
138    /// Panics if `src.len()` > `MAX_INLINE_BYTES`.
139    pub const fn new_inline(src: &str) -> GermanStr {
140        assert!(src.len() <= MAX_INLINE_BYTES);
141
142        let mut prefix = [0; 4];
143        let mut i = 0;
144        while i < src.len() && i < 4 {
145            prefix[i] = src.as_bytes()[i];
146            i += 1;
147        }
148
149        let mut buf = [0; 8];
150        let mut i = 4;
151        while i < src.len() && i < MAX_INLINE_BYTES {
152            buf[i - 4] = src.as_bytes()[i];
153            i += 1;
154        }
155
156        GermanStr {
157            len: src.len() as u32,
158            prefix,
159            last8: Last8 { buf },
160        }
161    }
162
163    #[inline(always)]
164    /// Returns the pointer to the heap-allocated buffer, if the `GermanStr`
165    /// isn't inlined.
166    /// In the actual GermanStr, 1 bit of the pointer is stolen to store
167    /// whether the heap allocation is shared or owned. Here, that bit is
168    /// reset to its default value before the pointer is returned.
169    /// `GermanStr::is_shared` can be used if you want to access that bit's
170    /// value.
171    pub fn heap_ptr(&self) -> Option<NonNull<u8>> {
172        self.heap_ointer()
173            .map(|ointer| ointer.as_non_null())
174    }
175
176    #[inline(always)]
177    /// Safe accessor for `self.last8.ptr`.
178    fn heap_ointer(&self) -> Option<ointers::NotNull<u8, 0, false, 1>> {
179        if self.len as usize > MAX_INLINE_BYTES {
180            Some(unsafe {
181                    // Safety: self.len > MAX_INLINE_BYTES => self isn't inlined.
182                    self.last8.ptr
183            })
184        } else {
185            None
186        }
187    }
188
189
190    #[inline(always)]
191    /// Returns whether `self` is heap-allocated, and the buffer possibly
192    /// shared with other instances, as after calling `leaky_shared_clone`.
193    pub fn has_shared_buffer(&self) -> bool {
194        self.heap_ointer().is_some_and(|ptr| ptr.stolen() != OWNED_PTR)
195    }
196
197    #[inline]
198    /// Clones `self`, reusing the same heap-allocated buffer (unless `self`
199    /// is inlined).
200    ///
201    /// After calling this method, the heap buffer will not be freed when
202    /// `self` is `Drop`ped: you are responsible for manually managing
203    /// memory by calling `GermanStr::free` exactly once per heap buffer.
204    ///
205    /// Even after calling this method once, it should be called instead of
206    /// clone to make new copies that reuse the same buffer: calling `clone()`
207    /// will always create a new copy of the buffer.
208    ///
209    /// This can save memory and increase performance in the case where you
210    /// have many equal `GermanStr` longer than `MAX_INLINE_BYTES`.
211    pub fn leaky_shared_clone(&mut self) -> Self {
212        if self.is_heap_allocated() {
213            unsafe {
214                self.last8.ptr = self.last8.ptr.steal(SHARED_PTR);
215            }
216        }
217        GermanStr {
218            len: self.len,
219            prefix: self.prefix,
220            last8: self.last8,
221        }
222    }
223
224    /// Should be called to free the heap buffer of a shared `GermanStr`.
225    ///
226    /// # Safety
227    /// * `self` should be heap-allocated and not inlined (you can check with
228    /// `GermanStr::is_heap_allocated`).
229    /// * You should only free each buffer once.
230    ///
231    /// However, `free()`ing a heap allocated but non-shared `GermanStr` is
232    /// safe and equivalent to dropping it.
233    ///
234    /// To avoid double frees, you can simply store a set of freed pointers.
235    /// ```no_run
236    /// use std::collections::BTreeSet;
237    /// # use german_str::GermanStr;
238    ///
239    /// let mut freed = BTreeSet::new();
240    /// # let german_str: Vec<GermanStr> = Vec::new();
241    /// for s in german_str {
242    ///     let Some(ptr) = s.heap_ptr() else {
243    ///         continue; // skip inlined GermanStr
244    ///     };
245    ///     if freed.insert(ptr) {
246    ///         unsafe {
247    ///             // Safety:
248    ///             // 1. s is heap-allocated or s.heap_ptr() would be None.
249    ///             // 2. If ptr had already been freed, insert()'ing it would've returned false.
250    ///             s.free();
251    ///         }
252    ///     } else {
253    ///         std::mem::forget(s);
254    ///     }
255    /// }
256    /// ```
257    pub unsafe fn free(mut self) {
258        unsafe {
259            // Safety:
260            // the caller is responsible for checking that `self` isn't inlined.
261            self.last8.ptr = self.last8.ptr.steal(OWNED_PTR);
262        }
263        core::mem::drop(self)
264    }
265
266    #[inline]
267    /// Returns a slice containing the first 4 bytes of a `GermanStr`.
268    /// Can be used for comparisons and ordering as is.
269    /// Since an UTF-8 char can consist of 1-4 bytes, this slice can represent
270    /// anywhere from 1 to 4 chars, and potentially only part of the last char.
271    pub fn prefix_bytes_slice(&self) -> &[u8] {
272        let prefix_len = self.len().min(4);
273        &self.prefix[..prefix_len]
274    }
275
276    #[inline(always)]
277    /// Returns an array containing the first 4 bytes of a `GermanStr`.
278    /// If the string is shorter than 4 bytes, extra bytes are set to 0.
279    /// Can be used for comparisons and ordering as is.
280    /// Since an UTF-8 char can consist of 1-4 bytes, this array can represent
281    /// anywhere from 1 to 4 chars, and potentially only part of the last char.
282    pub const fn prefix_bytes_array(&self) -> [u8; 4] {
283        self.prefix
284    }
285
286    #[inline]
287    /// Returns a slice containing every byte of a `GermanStr`, except the first 4.
288    pub fn suffix_bytes_slice(&self) -> &[u8] {
289        let suffix_len = self.len().saturating_sub(4);
290        if self.len as usize > MAX_INLINE_BYTES {
291            unsafe {
292                // Safety:
293                // self.len  > MAX_INLINE_BYTES => self.last8 is heap ptr.
294                let ptr = self.last8.ptr.as_non_null().as_ptr();
295
296                // Safety:
297                // 1. The data is part of a single object.
298                // 2. Pointer is checked for null at alloc.
299                // 3. ptr has the correct offset for the length
300                // 4. Heap values are properly initialized.
301                // 5. Values in the slice are never mutated.
302                slice::from_raw_parts(ptr.add(4), suffix_len)
303            }
304        } else {
305            unsafe {
306                &self.last8.buf[0..suffix_len]
307            }
308        }
309    }
310
311    #[inline(always)]
312    pub fn as_str(&self) -> &str {
313        Deref::deref(self)
314    }
315
316    #[allow(clippy::inherent_to_string_shadow_display)]
317    #[inline(always)]
318    pub fn to_string(&self) -> String {
319        self.as_str().to_owned()
320    }
321
322    #[inline(always)]
323    pub const fn len(&self) -> usize {
324        self.len as usize
325    }
326
327    #[inline(always)]
328    pub const fn is_empty(&self) -> bool  {
329        self.len == 0
330    }
331
332    #[inline(always)]
333    /// Returns whether a heap allocation is used to store the string.
334    pub const fn is_heap_allocated(&self) -> bool {
335        self.len as usize > MAX_INLINE_BYTES
336    }
337
338    #[inline(always)]
339    /// Returns whether the string is stored entirely within `self`, without a heap allocation.
340    pub const fn is_inlined(&self) -> bool {
341        !self.is_heap_allocated()
342    }
343}
344
345impl Clone for GermanStr {
346    #[inline]
347    fn clone(&self) -> Self {
348        if let Some(self_ptr) = self.heap_ptr() {
349            let (ptr, layout) = unsafe {
350                // Safety: If len was too high for this layout, we couldn't
351                // have made self in the first place.
352                let layout = Layout::array::<u8>(self.len()).unwrap_unchecked();
353
354                // Safety: layout is not zero-sized, otherwise we would store the string inplace.
355                let ptr = alloc::alloc::alloc(layout);
356                (ptr, layout)
357            };
358            let Some(ptr) = NonNull::new(ptr) else {
359                alloc::alloc::handle_alloc_error(layout);
360            };
361            unsafe {
362                // Safety:
363                //   1. Both pointers are valid.
364                //   2. *_ u8 is always aligned.
365                //   3. The 2 regions can't overlap since they belong to different objects.
366                ptr::copy_nonoverlapping(
367                    self_ptr.as_ptr(),
368                    ptr.as_ptr(),
369                    self.len(),
370                );
371            }
372            let ointer = unsafe {
373                // Safety: see Last8.ptr declaration.
374                ointers::NotNull::new_stealing(ptr, OWNED_PTR)
375            };
376            GermanStr {
377                prefix: self.prefix,
378                len: self.len,
379                last8: Last8 { ptr: ointer },
380            }
381        } else {
382            GermanStr {
383                len: self.len,
384                prefix: self.prefix,
385                last8: self.last8,
386            }
387        }
388    }
389}
390
391impl Drop for GermanStr {
392    #[inline]
393    fn drop(&mut self) {
394        let ptr = match self.heap_ptr() {
395            Some(ptr) if !self.has_shared_buffer() => ptr,
396            Some(_) | None => return,
397            // If the heap buffer is shared, or the string is inlined,
398            // dropping should be a no-op.
399        };
400        unsafe {
401            // Safety: this call can only fail if self.len is too long.
402            // We can only create a long `GermanStr` using GermanStr::new: if `self.len`
403            // was too long, we'd get an error when we try to create the GermanStr.
404            let layout = Layout::array::<u8>(self.len as usize).unwrap_unchecked();
405            alloc::alloc::dealloc(ptr.as_ptr(), layout);
406        }
407    }
408}
409
410// Safety: According to the rustonomicon, "something can safely be Send unless it shares mutable
411// state with something else without enforcing exclusive access to it."
412// The `ptr` is never shared between `GermanStr`, so there's no shared mutable state.
413unsafe impl Send for GermanStr {}
414
415// Safety: Again, according to the rustonomicon, there's no issue here since GermanStr are
416// immutable.
417unsafe impl Sync for GermanStr {}
418
419impl Deref for GermanStr {
420    type Target = str;
421
422    #[inline(always)]
423    fn deref(&self) -> &str {
424        let ptr = self.heap_ptr()
425            .unwrap_or_else(|| unsafe {
426                // Safety:
427                // self.prefix can't be null since it comes from &self.
428                NonNull::new_unchecked(self.prefix.as_ptr().cast_mut())
429            });
430        unsafe {
431            // Safety:
432            // * Since we're making a &[u8], it is guaranteed to be aligned.
433            // * The pointer used is NonNull and part of a single object (self
434            // or the heap buffer).
435            // * The len of the slice is correct.
436            // * The len is shorter than isize::MAX (2^63 - 1, MAX_LEN == 2^32).
437            // * ptr + len < isize::MAX, or the heap buffer/struct would overflow usize::MAX.
438            let slice = slice::from_raw_parts(ptr.as_ptr(), self.len as usize);
439
440            // Safety:
441            // A `GermanStr` is guaranteed to be a valid UTF8 string,
442            // since it can only be constructed from an impl AsRef<str>,
443            // a String, or a Writer that accepts &str.
444            core::str::from_utf8_unchecked(slice)
445        }
446    }
447}
448
449impl AsRef<str> for GermanStr {
450    #[inline(always)]
451    fn as_ref(&self) -> &str {
452        Deref::deref(self)
453    }
454}
455
456impl PartialEq<GermanStr> for GermanStr {
457    #[inline(always)]
458    fn eq(&self, other: &GermanStr) -> bool {
459        let prefixes_equal = self.prefix == other.prefix;
460        if !prefixes_equal {
461            return false;
462        } else if self.len <= 4 && other.len <= 4 {
463            return true;
464        }
465
466        if self.is_inlined() && other.is_inlined() {
467            return unsafe {
468                // Safety: obviously both strings are stored inline.
469                self.last8.buf == other.last8.buf
470            };
471        }
472
473        return self.suffix_bytes_slice() == other.suffix_bytes_slice();
474    }
475}
476
477impl Eq for GermanStr {}
478
479impl Ord for GermanStr {
480    #[inline]
481    fn cmp(&self, other: &Self) -> cmp::Ordering {
482        self.prefix
483            .cmp(&other.prefix)
484            .then_with(||
485                if self.len <= 4 && other.len <= 4 {
486                    cmp::Ordering::Equal
487                } else if self.is_inlined() && other.is_inlined() {
488                    unsafe {
489                        // Safety: obviously both strings are stored inline.
490                        self.last8.buf.cmp(&other.last8.buf)
491                    }
492                } else {
493                    self.suffix_bytes_slice().cmp(other.suffix_bytes_slice())
494                }
495            )
496    }
497}
498
499impl Default for GermanStr {
500    #[inline(always)]
501    fn default() -> GermanStr {
502        GermanStr {
503            len: 0,
504            prefix: [0; 4],
505            last8: Last8 { buf: [0; 8] },
506        }
507    }
508}
509
510impl FromStr for GermanStr {
511    type Err = InitError;
512
513    #[inline]
514    fn from_str(s: &str) -> Result<GermanStr, Self::Err> {
515        GermanStr::new(s)
516    }
517}
518
519impl Borrow<str> for GermanStr {
520    #[inline(always)]
521    fn borrow(&self) -> &str {
522        self.as_str()
523    }
524}
525
526impl core::fmt::Debug for GermanStr {
527    #[inline]
528    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
529        core::fmt::Debug::fmt(self.as_str(), f)
530    }
531}
532
533
534impl core::fmt::Display for GermanStr {
535    #[inline]
536    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
537        core::fmt::Display::fmt(self.as_str(), f)
538    }
539}
540
541impl core::fmt::Display for InitError {
542    #[inline]
543    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
544        core::fmt::Display::fmt(
545            match self {
546                InitError::TooLong => "Tried to initialize a GermanStr longer than 4GB.",
547            },
548            f
549        )
550    }
551}
552
553impl core::hash::Hash for GermanStr {
554    #[inline(always)]
555    fn hash<H: core::hash::Hasher>(&self, hasher: &mut H) {
556        self.as_str().hash(hasher);
557    }
558}
559
560
561impl PartialOrd for GermanStr {
562    #[inline(always)]
563    fn partial_cmp(&self, other: &GermanStr) -> Option<core::cmp::Ordering> {
564        Some(self.cmp(other))
565    }
566}
567
568impl PartialEq<str> for GermanStr {
569    #[inline(always)]
570    fn eq(&self, other: &str) -> bool {
571        self.prefix == str_prefix::<&str>(other) &&
572        self.suffix_bytes_slice() == str_suffix::<&str>(&other)
573    }
574}
575
576impl PartialEq<GermanStr> for str {
577    #[inline(always)]
578    fn eq(&self, other: &GermanStr) -> bool {
579        other.prefix == str_prefix::<&str>(self) &&
580        other.suffix_bytes_slice() == str_suffix::<&str>(&self)
581    }
582}
583
584impl<'a> PartialEq<&'a str> for GermanStr {
585    #[inline(always)]
586    fn eq(&self, other: &&'a str) -> bool {
587        self.prefix == str_prefix::<&str>(other) &&
588        self.suffix_bytes_slice() == str_suffix::<&str>(&other)
589    }
590}
591
592impl<'a> PartialEq<GermanStr> for &'a str {
593    #[inline(always)]
594    fn eq(&self, other: &GermanStr) -> bool {
595        other.prefix == str_prefix::<&str>(self) &&
596        other.suffix_bytes_slice() == str_suffix::<&str>(&self)
597    }
598}
599
600impl PartialEq<String> for GermanStr {
601    #[inline(always)]
602    fn eq(&self, other: &String) -> bool {
603        self.prefix == str_prefix::<&str>(other) &&
604        self.suffix_bytes_slice() == str_suffix::<&str>(&other)
605    }
606}
607
608impl PartialEq<GermanStr> for String {
609    #[inline(always)]
610    fn eq(&self, other: &GermanStr) -> bool {
611        other.prefix == str_prefix::<&str>(self) &&
612        other.suffix_bytes_slice() == str_suffix::<&str>(&self)
613    }
614}
615
616impl<'a> PartialEq<&'a String> for GermanStr {
617    #[inline(always)]
618    fn eq(&self, other: &&'a String) -> bool {
619        self.prefix == str_prefix::<&str>(other) &&
620        self.suffix_bytes_slice() == str_suffix::<&str>(&other)
621    }
622}
623
624impl<'a> PartialEq<GermanStr> for &'a String {
625    #[inline(always)]
626    fn eq(&self, other: &GermanStr) -> bool {
627        other.prefix == str_prefix::<&str>(self) &&
628        other.suffix_bytes_slice() == str_suffix::<&str>(&self)
629    }
630}
631
632impl TryFrom<&str> for GermanStr {
633    type Error = InitError;
634
635    #[inline]
636    fn try_from(s: &str) -> Result<GermanStr, InitError> {
637        GermanStr::new(s)
638        }
639    }
640
641impl TryFrom<&mut str> for GermanStr {
642    type Error = InitError;
643
644    #[inline]
645    fn try_from(s: &mut str) -> Result<GermanStr,InitError> {
646        GermanStr::new(s)
647    }
648}
649
650impl TryFrom<&String> for GermanStr {
651    type Error = InitError;
652
653    #[inline]
654    fn try_from(s: &String) -> Result<GermanStr,InitError> {
655        GermanStr::new(s)
656    }
657}
658
659impl TryFrom<String> for GermanStr {
660    type Error = InitError;
661
662    #[inline(always)]
663    fn try_from(text: String) -> Result<Self, Self::Error> {
664        Self::new(text)
665    }
666}
667
668impl TryFrom<Box<str>> for GermanStr {
669    type Error = InitError;
670
671    #[inline(always)]
672    fn try_from(s: Box<str>) -> Result<GermanStr, Self::Error> {
673        GermanStr::new(s)
674    }
675}
676
677impl TryFrom<Arc<str>> for GermanStr {
678    type Error = InitError;
679
680    #[inline(always)]
681    fn try_from(s: Arc<str>) -> Result<GermanStr, Self::Error> {
682        GermanStr::new(s)
683    }
684}
685
686impl From<GermanStr> for Arc<str> {
687    #[inline(always)]
688    fn from(text: GermanStr) -> Self {
689        text.as_str().into()
690    }
691}
692
693impl<'a> TryFrom<Cow<'a, str>> for GermanStr {
694    type Error = InitError;
695
696    #[inline]
697    fn try_from(s: Cow<'a, str>) -> Result<GermanStr,InitError> {
698        GermanStr::new(s)
699    }
700}
701
702impl From<GermanStr> for String {
703    #[inline(always)]
704    fn from(text: GermanStr) -> Self {
705        text.as_str().into()
706    }
707}
708
709#[inline]
710/// Returns the first 4 bytes of a string.
711/// If the string has less than 4 bytes, extra bytes are set to 0.
712pub fn str_prefix<T>(src: impl AsRef<str>) -> [u8; 4] {
713    let src_bytes = src.as_ref().as_bytes();
714    let prefix_len = src_bytes.len().min(4);
715    let mut bytes = [0; 4];
716    bytes[..prefix_len].copy_from_slice(&src_bytes[..prefix_len]);
717    bytes
718}
719
720#[inline]
721/// Returns a slice to every byte of a string, skipping the first 4.
722pub fn str_suffix<T>(src: &impl AsRef<str>) -> &[u8] {
723    src.as_ref().as_bytes().get(4..).unwrap_or_default()
724}
725
726/// Almost identical to `ToString`, but converts to `GermanStr` instead.
727pub trait ToGermanStr {
728    fn to_german_str(&self) -> GermanStr;
729}
730
731#[doc(hidden)]
732pub struct Writer {
733    len: usize,
734    inline: [u8; MAX_INLINE_BYTES],
735    heap: String,
736}
737
738impl Writer {
739    #[must_use]
740    pub const fn new() -> Self {
741        Writer {
742            len: 0,
743            inline: [0; MAX_INLINE_BYTES],
744            heap: String::new(),
745        }
746    }
747
748    fn push_str(&mut self, s: &str) -> Result<(), InitError> {
749        let old_len = self.len;
750        self.len += s.len();
751        if self.len > MAX_LEN {
752            return Err(InitError::TooLong);
753        }
754        if self.len <= MAX_INLINE_BYTES {
755            // we are still inline after the write
756            self.inline[old_len..self.len].copy_from_slice(s.as_bytes());
757        } else if old_len <= MAX_INLINE_BYTES {
758            // we need to switch from inline to heap
759            self.heap.reserve(self.len);
760            unsafe {
761                // SAFETY: inline data is guaranteed to be valid utf8 for previously
762                // written bytes since this is the only &mut method and we write an
763                // entire &str at each call, which is valid UTF8 bytes.
764                self.heap
765                    .as_mut_vec()
766                    .extend_from_slice(&self.inline[..old_len]);
767            }
768            self.heap.push_str(s);
769        } else {
770            self.heap.push_str(s);
771        }
772        Ok(())
773    }
774}
775
776impl fmt::Write for Writer {
777    #[inline]
778    fn write_str(&mut self, s: &str) -> fmt::Result {
779        self.push_str(s)
780            .map_err(|_| fmt::Error::default())
781    }
782}
783
784/// Formats arguments to a [`GermanStr`], potentially without allocating.
785///
786/// See [`alloc::format!`] or [`format_args!`] for syntax documentation.
787#[macro_export]
788macro_rules! format_german_str {
789    ($($tt:tt)*) => {{
790        use ::core::fmt::Write;
791        let mut w = $crate::Writer::new();
792        w.write_fmt(format_args!($($tt)*))
793            .expect("tried to format_german_str a GermanStr bigger than the maximum GermanStr size");
794        $crate::GermanStr::from(w)
795    }};
796}
797
798impl From<Writer> for GermanStr {
799    fn from(value: Writer) -> Self {
800        if value.len <= MAX_INLINE_BYTES {
801            let mut prefix = [0; 4];
802            prefix.clone_from_slice(&value.inline[0..4]);
803            let mut last8 = [0; 8];
804            last8.clone_from_slice(&value.inline[4..MAX_INLINE_BYTES]);
805            GermanStr {
806                len: value.len as u32,
807                prefix,
808                last8: Last8 { buf: last8 },
809            }
810        } else {
811            let heap_ref = value.heap.leak(); // avoid copying the str
812            let non_null = unsafe {
813                NonNull::new_unchecked(heap_ref.as_mut_ptr())
814            };
815            let ointer = unsafe {
816                // Safety: see Last8.ptr declaration.
817                ointers::NotNull::new_stealing(non_null, OWNED_PTR)
818            };
819            GermanStr {
820                len: value.len as u32,
821                prefix: str_prefix::<&str>(heap_ref),
822                last8: Last8 { ptr: ointer },
823            }
824        }
825    }
826}
827
828impl<T> ToGermanStr for T
829where
830    T: fmt::Display + ?Sized,
831{
832    fn to_german_str(&self) -> GermanStr {
833        format_german_str!("{}", self)
834    }
835}
836
837#[cfg(feature = "arbitrary")]
838impl<'a> arbitrary::Arbitrary<'a> for GermanStr {
839    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> Result<Self, arbitrary::Error> {
840        let s = <&str>::arbitrary(u)?;
841        Ok(GermanStr::new(s).expect("BUG in arbitrary implementation of GermanStr. Please report it at github.com/ostnam/german-str/issues"))
842    }
843
844    fn size_hint(_: usize) -> (usize, Option<usize>) {
845        (0, Some(MAX_LEN))
846    }
847}
848
849#[cfg(feature = "serde")]
850mod serde {
851    use alloc::string::String;
852    use alloc::vec::Vec;
853    use core::fmt;
854
855    use serde::de::{Deserializer, Error, Unexpected, Visitor};
856
857    use crate::GermanStr;
858
859    fn deserialize<'de: 'a, 'a, D>(deserializer: D) -> Result<GermanStr, D::Error>
860    where
861        D: Deserializer<'de>,
862    {
863        struct GermanStrVisitor;
864
865        impl<'a> Visitor<'a> for GermanStrVisitor {
866            type Value = GermanStr;
867
868            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
869                formatter.write_str("a string")
870            }
871
872            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
873            where
874                E: Error,
875            {
876                GermanStr::new(v).map_err(Error::custom)
877            }
878
879            fn visit_borrowed_str<E>(self, v: &'a str) -> Result<Self::Value, E>
880            where
881                E: Error,
882            {
883                GermanStr::new(v).map_err(Error::custom)
884            }
885
886            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
887            where
888                E: Error,
889            {
890                GermanStr::new(v).map_err(Error::custom)
891            }
892
893            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
894            where
895                E: Error,
896            {
897                match core::str::from_utf8(v) {
898                    Ok(s) => GermanStr::new(s).map_err(Error::custom),
899                    Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
900                }
901            }
902
903            fn visit_borrowed_bytes<E>(self, v: &'a [u8]) -> Result<Self::Value, E>
904            where
905                E: Error,
906            {
907                match core::str::from_utf8(v) {
908                    Ok(s) => GermanStr::new(s).map_err(Error::custom),
909                    Err(_) => Err(Error::invalid_value(Unexpected::Bytes(v), &self)),
910                }
911            }
912
913            fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
914            where
915                E: Error,
916            {
917                match String::from_utf8(v) {
918                    Ok(s) => GermanStr::new(s).map_err(Error::custom),
919                    Err(e) => Err(Error::invalid_value(
920                        Unexpected::Bytes(&e.into_bytes()),
921                        &self,
922                    )),
923                }
924            }
925        }
926
927        deserializer.deserialize_str(GermanStrVisitor)
928    }
929
930    impl serde::Serialize for GermanStr {
931        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
932        where
933            S: serde::Serializer,
934        {
935            self.as_str().serialize(serializer)
936        }
937    }
938
939    impl<'de> serde::Deserialize<'de> for GermanStr {
940        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
941        where
942            D: serde::Deserializer<'de>,
943        {
944            deserialize(deserializer)
945        }
946    }
947}