Skip to main content

fstr/
lib.rs

1//! # FStr: a stack-allocated fixed-length string type
2//!
3//! This crate provides a new type wrapping `[u8; N]` to handle a stack-allocated byte array as a
4//! fixed-length, [`String`]-like owned type through common traits including `Display`, `PartialEq`,
5//! and `Deref<Target = str>`.
6//!
7//! ```rust
8//! use fstr::FStr;
9//!
10//! let x = FStr::try_from(b"foo")?;
11//! println!("{}", x); // "foo"
12//! assert_eq!(x, "foo");
13//! assert_eq!(&x[..], "foo");
14//! assert_eq!(&x as &str, "foo");
15//! assert!(!x.is_empty());
16//! assert!(x.is_ascii());
17//!
18//! let mut y = FStr::try_from(b"bar")?;
19//! assert_eq!(y, "bar");
20//! y.make_ascii_uppercase();
21//! assert_eq!(y, "BAR");
22//!
23//! const K: FStr<8> = FStr::from_str_unwrap("constant");
24//! assert_eq!(K, "constant");
25//! # Ok::<_, core::str::Utf8Error>(())
26//! ```
27//!
28//! Unlike [`String`] and [`arrayvec::ArrayString`], which keep track of the length of the stored
29//! string, this type has the same binary representation as the underlying `[u8; N]` and, therefore,
30//! can only manage fixed-length strings. The type parameter `N` specifies the exact length (in
31//! bytes) of a concrete type, and each concrete type holds only string values of that size.
32//!
33//! [`arrayvec::ArrayString`]: https://docs.rs/arrayvec/latest/arrayvec/struct.ArrayString.html
34//!
35//! ```rust
36//! # use fstr::FStr;
37//! let s = "Lorem Ipsum ✨";
38//! assert_eq!(s.len(), 15);
39//! assert!(s.parse::<FStr<15>>().is_ok()); // just right
40//! assert!(s.parse::<FStr<10>>().is_err()); // too small
41//! assert!(s.parse::<FStr<20>>().is_err()); // too large
42//! ```
43//!
44//! ```compile_fail
45//! # use fstr::FStr;
46//! let x: FStr<10> = FStr::from_str_unwrap("helloworld");
47//! let y: FStr<12> = FStr::from_str_unwrap("helloworld  ");
48//!
49//! // This code does not compile because `FStr` of different lengths cannot mix.
50//! if x != y {
51//!     unreachable!();
52//! }
53//! ```
54//!
55//! Variable-length string operations are partially supported by utilizing a C-style NUL-terminated
56//! buffer and some helper methods.
57//!
58//! ```rust
59//! # use fstr::FStr;
60//! let mut buffer = FStr::<24>::from_fmt(format_args!("&#x{:x};", b'@'), b'\0')?;
61//! assert_eq!(buffer, "&#x40;\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
62//!
63//! let c_str = buffer.slice_to_terminator('\0');
64//! assert_eq!(c_str, "&#x40;");
65//!
66//! use core::fmt::Write as _;
67//! write!(buffer.writer_at(c_str.len()), " COMMERCIAL AT")?;
68//! assert_eq!(buffer.slice_to_terminator('\0'), "&#x40; COMMERCIAL AT");
69//! # assert_eq!(buffer, "&#x40; COMMERCIAL AT\0\0\0\0");
70//! # Ok::<_, core::fmt::Error>(())
71//! ```
72//!
73//! ## Crate features
74//!
75//! - `std` (enabled by default) enables the integration with [`std`]. Disable default features to
76//!   operate this crate under `no_std` environments.
77//! - `alloc` (implied by `std`) enables the integration with [`alloc`].
78//! - `serde` enables the serialization and deserialization of `FStr`through [`serde`].
79
80#![cfg_attr(not(feature = "std"), no_std)]
81#![cfg_attr(docsrs, feature(doc_cfg))]
82
83#[cfg(feature = "alloc")]
84extern crate alloc;
85
86#[cfg(not(feature = "std"))]
87use core as std;
88use std::{borrow, error, fmt, hash, mem, ops, ptr, str};
89
90/// A stack-allocated fixed-length string type.
91///
92/// This type has exactly the same size and binary representation as the inner `[u8; N]` buffer.
93///
94/// See [the crate-level documentation](crate) for details.
95#[derive(Copy, Clone, Eq, Ord, PartialOrd)]
96#[repr(transparent)]
97pub struct FStr<const N: usize> {
98    inner: [u8; N],
99}
100
101impl<const N: usize> FStr<N> {
102    /// The length of the content in bytes.
103    pub const LENGTH: usize = N;
104
105    /// Returns a string slice of the content.
106    pub const fn as_str(&self) -> &str {
107        debug_assert!(str::from_utf8(&self.inner).is_ok());
108        // SAFETY: constructors must guarantee that `inner` is a valid UTF-8 sequence.
109        unsafe { str::from_utf8_unchecked(&self.inner) }
110    }
111
112    /// Returns a mutable string slice of the content.
113    pub const fn as_mut_str(&mut self) -> &mut str {
114        debug_assert!(str::from_utf8(&self.inner).is_ok());
115        // SAFETY: constructors must guarantee that `inner` is a valid UTF-8 sequence.
116        unsafe { str::from_utf8_unchecked_mut(&mut self.inner) }
117    }
118
119    /// Returns a reference to the underlying byte array.
120    pub const fn as_bytes(&self) -> &[u8; N] {
121        &self.inner
122    }
123
124    /// Extracts the underlying byte array.
125    pub const fn into_inner(self) -> [u8; N] {
126        self.inner
127    }
128
129    /// Creates a value from a fixed-length byte array.
130    ///
131    /// # Errors
132    ///
133    /// Returns `Err` if the bytes passed in are not valid UTF-8.
134    ///
135    /// # Examples
136    ///
137    /// ```rust
138    /// # use fstr::FStr;
139    /// let x = FStr::from_inner(*b"foo")?;
140    /// assert_eq!(x, "foo");
141    /// # Ok::<_, core::str::Utf8Error>(())
142    /// ```
143    pub const fn from_inner(utf8_bytes: [u8; N]) -> Result<Self, str::Utf8Error> {
144        match str::from_utf8(&utf8_bytes) {
145            Ok(_) => Ok(Self { inner: utf8_bytes }),
146            Err(e) => Err(e),
147        }
148    }
149
150    /// Creates a value from a byte array without checking that the bytes are valid UTF-8.
151    ///
152    /// # Safety
153    ///
154    /// The byte array passed in must contain a valid UTF-8 byte sequence.
155    pub const unsafe fn from_inner_unchecked(utf8_bytes: [u8; N]) -> Self {
156        debug_assert!(str::from_utf8(&utf8_bytes).is_ok());
157        Self { inner: utf8_bytes }
158    }
159
160    /// A `const`-friendly equivalent of `Self::from_str(s).unwrap()`.
161    ///
162    /// # Examples
163    ///
164    /// ```rust
165    /// # use fstr::FStr;
166    /// use core::str::FromStr;
167    ///
168    /// const K: FStr<3> = FStr::from_str_unwrap("foo");
169    /// assert_eq!(K, FStr::from_str("foo").unwrap());
170    /// ```
171    pub const fn from_str_unwrap(s: &str) -> Self {
172        match Self::try_from_str(s) {
173            Ok(t) => t,
174            _ => panic!("invalid byte length"),
175        }
176    }
177
178    /// Creates a value from a string slice in the `const` context.
179    const fn try_from_str(s: &str) -> Result<Self, LengthError> {
180        match Self::copy_slice_to_array(s.as_bytes()) {
181            // SAFETY: ok because `inner` contains the whole content of a string slice
182            Ok(inner) => Ok(unsafe { Self::from_inner_unchecked(inner) }),
183            Err(e) => Err(e),
184        }
185    }
186
187    /// Creates a value from a byte slice in the `const` context.
188    const fn try_from_slice(s: &[u8]) -> Result<Self, FromSliceError> {
189        match Self::copy_slice_to_array(s) {
190            Ok(inner) => match Self::from_inner(inner) {
191                Ok(t) => Ok(t),
192                Err(e) => Err(FromSliceError {
193                    kind: FromSliceErrorKind::Utf8(e),
194                }),
195            },
196            Err(e) => Err(FromSliceError {
197                kind: FromSliceErrorKind::Length(e),
198            }),
199        }
200    }
201
202    /// Creates a value from an arbitrary string but truncates or stretches the content.
203    ///
204    /// This function appends `filler` bytes to the end if the argument is shorter than the type's
205    /// length. The `filler` byte must be within the ASCII range. The argument is truncated, if
206    /// longer, at the closest character boundary to the type's length, with `filler` bytes
207    /// appended where necessary.
208    ///
209    /// # Panics
210    ///
211    /// Panics if `filler` is out of the ASCII range.
212    ///
213    /// # Examples
214    ///
215    /// ```rust
216    /// # use fstr::FStr;
217    /// assert_eq!(FStr::<5>::from_str_lossy("seasons", b' '), "seaso");
218    /// assert_eq!(FStr::<7>::from_str_lossy("seasons", b' '), "seasons");
219    /// assert_eq!(FStr::<9>::from_str_lossy("seasons", b' '), "seasons  ");
220    ///
221    /// assert_eq!("πŸ˜‚πŸ€ͺπŸ˜±πŸ‘»".len(), 16);
222    /// assert_eq!(FStr::<15>::from_str_lossy("πŸ˜‚πŸ€ͺπŸ˜±πŸ‘»", b'.'), "πŸ˜‚πŸ€ͺ😱...");
223    /// ```
224    pub const fn from_str_lossy(s: &str, filler: u8) -> Self {
225        if N == 0 {
226            return Self::from_ascii_filler(filler); // filler check done there
227        }
228        assert!(filler.is_ascii(), "filler byte must represent ASCII char");
229
230        let len = if s.len() <= N {
231            s.len()
232        } else if is_utf8_char_boundary(s.as_bytes()[N]) {
233            N
234        } else if is_utf8_char_boundary(s.as_bytes()[N - 1]) {
235            N - 1
236        } else if is_utf8_char_boundary(s.as_bytes()[N - 2]) {
237            N - 2
238        } else if is_utf8_char_boundary(s.as_bytes()[N - 3]) {
239            N - 3
240        } else {
241            unreachable!() // invalid UTF-8 sequence
242        };
243
244        let mut inner = [filler; N];
245        debug_assert!(len <= s.len() && len <= inner.len());
246        // SAFETY: ok because:
247        // - Pointers come from references and thus are valid and aligned.
248        // - `len` is at most the length of `s` and `inner`, so the source and destination are
249        //   valid for read/write of `len` bytes.
250        // - `s` and `inner` do not overlap because they are independent arrays.
251        unsafe { ptr::copy_nonoverlapping(s.as_ptr(), inner.as_mut_ptr(), len) };
252
253        // SAFETY: ok because `s` is from a string slice (truncated at a char boundary, if
254        // applicable) and `inner` consists of `s` and trailing ASCII fillers
255        unsafe { Self::from_inner_unchecked(inner) }
256    }
257
258    /// Creates a value that is filled with an ASCII byte.
259    ///
260    /// # Panics
261    ///
262    /// Panics if the argument is out of the ASCII range.
263    ///
264    /// # Examples
265    ///
266    /// ```rust
267    /// # use fstr::FStr;
268    /// assert_eq!(FStr::<3>::from_ascii_filler(b'.'), "...");
269    /// assert_eq!(FStr::<5>::from_ascii_filler(b'-'), "-----");
270    /// # assert_eq!(FStr::<0>::from_ascii_filler(b'\0'), "");
271    /// ```
272    pub const fn from_ascii_filler(filler: u8) -> Self {
273        assert!(filler.is_ascii(), "filler byte must represent ASCII char");
274        // SAFETY: ok because the array consists of ASCII bytes only
275        unsafe { Self::from_inner_unchecked([filler; N]) }
276    }
277
278    /// A deprecated synonym for [`FStr::from_ascii_filler`] retained for backward compatibility.
279    #[doc(hidden)]
280    #[deprecated(since = "0.2.12", note = "renamed to `from_ascii_filler`")]
281    pub const fn repeat(filler: u8) -> Self {
282        Self::from_ascii_filler(filler)
283    }
284
285    /// Returns a substring from the beginning to the specified terminator (if found) or to the end
286    /// (otherwise).
287    ///
288    /// This method extracts a string slice from the beginning to the first occurrence of the
289    /// `terminator` character. The resulting slice does not contain the `terminator` itself. This
290    /// method returns a slice containing the entire content if no `terminator` is found.
291    ///
292    /// # Examples
293    ///
294    /// ```rust
295    /// # use fstr::FStr;
296    /// let x = FStr::from_inner(*b"quick brown fox\n")?;
297    /// assert_eq!(x.slice_to_terminator(' '), "quick");
298    /// assert_eq!(x.slice_to_terminator('w'), "quick bro");
299    /// assert_eq!(x.slice_to_terminator('\n'), "quick brown fox");
300    /// assert_eq!(x.slice_to_terminator('🦊'), "quick brown fox\n");
301    /// # assert_eq!(FStr::from_inner([])?.slice_to_terminator(' '), "");
302    /// # Ok::<_, core::str::Utf8Error>(())
303    /// ```
304    pub fn slice_to_terminator(&self, terminator: char) -> &str {
305        match self.find(terminator) {
306            Some(i) => &self[..i],
307            _ => self,
308        }
309    }
310
311    /// A deprecated synonym for `FStr::writer_at(0)` retained for backward compatibility.
312    #[doc(hidden)]
313    #[deprecated(since = "0.2.13", note = "use `writer_at(0)` instead")]
314    pub fn writer(&mut self) -> Cursor<&mut Self> {
315        self.writer_at(0)
316    }
317
318    /// Returns a writer that writes `&str` into `self` through the [`fmt::Write`] trait.
319    ///
320    /// The writer starts at the specified `index` of `self` and overwrites the existing content as
321    /// `write_str` is called. This writer fails if too many bytes would be written. It also fails
322    /// when a `write_str` call would result in an invalid UTF-8 sequence by destroying an existing
323    /// multi-byte character. Due to the latter limitation, this writer is not very useful unless
324    /// `self` is filled with ASCII bytes only.
325    ///
326    /// # Panics
327    ///
328    /// Panics if the `index` does not point to a character boundary or is past the end of `self`.
329    ///
330    /// # Examples
331    ///
332    /// ```rust
333    /// # use fstr::FStr;
334    /// use core::fmt::Write as _;
335    ///
336    /// let mut a = FStr::<12>::from_ascii_filler(b'.');
337    /// write!(a.writer_at(0), "0x{:06x}!", 0x42)?;
338    /// assert_eq!(a, "0x000042!...");
339    ///
340    /// let mut b = FStr::<12>::from_ascii_filler(b'.');
341    /// write!(b.writer_at(2), "0x{:06x}!", 0x42)?;
342    /// assert_eq!(b, "..0x000042!.");
343    ///
344    /// let mut c = FStr::<12>::from_ascii_filler(b'.');
345    /// assert!(write!(c.writer_at(0), "{:016}", 1).is_err()); // buffer overflow
346    ///
347    /// let mut d = FStr::<12>::from_ascii_filler(b'.');
348    /// let mut w = d.writer_at(0);
349    /// write!(w, "πŸ₯Ί")?;
350    /// write!(w, "++")?;
351    /// assert_eq!(d, "πŸ₯Ί++......");
352    ///
353    /// assert!(d.writer_at(0).write_str("++").is_err()); // invalid UTF-8 sequence
354    /// assert_eq!(d, "πŸ₯Ί++......");
355    /// d.writer_at(0).write_str("----")?;
356    /// assert_eq!(d, "----++......");
357    /// # Ok::<_, core::fmt::Error>(())
358    /// ```
359    pub fn writer_at(&mut self, index: usize) -> Cursor<&mut Self> {
360        Cursor::with_position(index, self).expect("index must point to char boundary")
361    }
362
363    /// Creates a value from [`fmt::Arguments`], with `filler` bytes appended if the formatted
364    /// string is shorter than the type's length.
365    ///
366    /// The behavior of this function is different from [`FStr::from_str_lossy`] and [`write!`]
367    /// over [`FStr::writer_at`] in that it does not truncate the formatted string or result in a
368    /// partially written `FStr` value; when it returns `Ok`, the result contains the complete
369    /// content of the formatted string, with `filler` bytes appended where necessary.
370    ///
371    /// # Errors
372    ///
373    /// Returns `Err` if the formatted string is longer than the type's length.
374    ///
375    /// # Panics
376    ///
377    /// Panics if `filler` is out of the ASCII range.
378    ///
379    /// # Examples
380    ///
381    /// ```rust
382    /// # use fstr::FStr;
383    /// let x = FStr::<10>::from_fmt(format_args!("  {:04x}  ", 0x42), b'\0')?;
384    /// assert_eq!(x.slice_to_terminator('\0'), "  0042  ");
385    /// assert_eq!(x, "  0042  \0\0");
386    /// # Ok::<_, core::fmt::Error>(())
387    /// ```
388    pub fn from_fmt(args: fmt::Arguments<'_>, filler: u8) -> Result<Self, fmt::Error> {
389        assert!(filler.is_ascii(), "filler byte must represent ASCII char");
390
391        struct Writer<'s>(&'s mut [mem::MaybeUninit<u8>]);
392
393        impl fmt::Write for Writer<'_> {
394            fn write_str(&mut self, s: &str) -> fmt::Result {
395                if s.len() <= self.0.len() {
396                    let written;
397                    (written, self.0) = mem::take(&mut self.0).split_at_mut(s.len());
398                    // SAFETY: ok because &[T] and &[MaybeUninit<T>] have the same layout
399                    written.copy_from_slice(unsafe {
400                        mem::transmute::<&[u8], &[mem::MaybeUninit<u8>]>(s.as_bytes())
401                    });
402                    Ok(())
403                } else {
404                    Err(fmt::Error)
405                }
406            }
407        }
408
409        let mut inner = [const { mem::MaybeUninit::uninit() }; N];
410        let mut w = Writer(inner.as_mut_slice());
411        if fmt::Write::write_fmt(&mut w, args).is_ok() {
412            w.0.fill(mem::MaybeUninit::new(filler)); // initialize remaining part with `filler`s
413
414            // SAFETY: ok because [T; N] and [MaybeUninit<T>; N] have the same size and layout and
415            // the entire array has been initialized with valid `&str`s and ASCII `filler`s
416            Ok(unsafe {
417                Self::from_inner_unchecked(
418                    mem::transmute_copy::<[mem::MaybeUninit<u8>; N], [u8; N]>(&inner),
419                )
420            })
421        } else {
422            // not dropping partially written data because:
423            const _STATIC_ASSERT: () = assert!(!mem::needs_drop::<u8>(), "u8 never needs drop");
424            Err(fmt::Error)
425        }
426    }
427}
428
429/// Helper functions
430impl<const N: usize> FStr<N> {
431    /// Creates a fixed-length array by copying from a slice.
432    const fn copy_slice_to_array(s: &[u8]) -> Result<[u8; N], LengthError> {
433        if s.len() == N {
434            // SAFETY: ok because `s.len() == N`
435            Ok(unsafe { *s.as_ptr().cast::<[u8; N]>() })
436        } else {
437            Err(LengthError {
438                actual: s.len(),
439                expected: N,
440            })
441        }
442    }
443}
444
445impl<const N: usize> ops::Deref for FStr<N> {
446    type Target = str;
447
448    fn deref(&self) -> &Self::Target {
449        self.as_str()
450    }
451}
452
453impl<const N: usize> ops::DerefMut for FStr<N> {
454    fn deref_mut(&mut self) -> &mut str {
455        self.as_mut_str()
456    }
457}
458
459impl<const N: usize> Default for FStr<N> {
460    /// Returns a fixed-length string value filled with white spaces (`U+0020`).
461    ///
462    /// # Examples
463    ///
464    /// ```rust
465    /// # use fstr::FStr;
466    /// assert_eq!(FStr::<4>::default(), "    ");
467    /// assert_eq!(FStr::<8>::default(), "        ");
468    /// ```
469    fn default() -> Self {
470        Self::from_ascii_filler(b' ')
471    }
472}
473
474impl<const N: usize> fmt::Debug for FStr<N> {
475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476        f.debug_struct(
477            match FStr::<32>::from_fmt(format_args!("FStr<{}>", N), b'\0') {
478                Ok(ref buffer) => buffer.slice_to_terminator('\0'),
479                Err(_) => "FStr", // unreachable
480            },
481        )
482        .field("inner", &self.as_str())
483        .finish()
484    }
485}
486
487impl<const N: usize> fmt::Display for FStr<N> {
488    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489        fmt::Display::fmt(self.as_str(), f)
490    }
491}
492
493impl<const N: usize> PartialEq for FStr<N> {
494    fn eq(&self, other: &FStr<N>) -> bool {
495        self.as_str().eq(other.as_str())
496    }
497}
498
499impl<const N: usize> PartialEq<str> for FStr<N> {
500    fn eq(&self, other: &str) -> bool {
501        self.as_str().eq(other)
502    }
503}
504
505impl<const N: usize> PartialEq<FStr<N>> for str {
506    fn eq(&self, other: &FStr<N>) -> bool {
507        self.eq(other.as_str())
508    }
509}
510
511impl<const N: usize> PartialEq<&str> for FStr<N> {
512    fn eq(&self, other: &&str) -> bool {
513        self.as_str().eq(*other)
514    }
515}
516
517impl<const N: usize> PartialEq<FStr<N>> for &str {
518    fn eq(&self, other: &FStr<N>) -> bool {
519        self.eq(&other.as_str())
520    }
521}
522
523impl<const N: usize> hash::Hash for FStr<N> {
524    fn hash<H: hash::Hasher>(&self, hasher: &mut H) {
525        self.as_str().hash(hasher)
526    }
527}
528
529impl<const N: usize> borrow::Borrow<str> for FStr<N> {
530    fn borrow(&self) -> &str {
531        self.as_str()
532    }
533}
534
535impl<const N: usize> borrow::BorrowMut<str> for FStr<N> {
536    fn borrow_mut(&mut self) -> &mut str {
537        self.as_mut_str()
538    }
539}
540
541impl<const N: usize> AsRef<str> for FStr<N> {
542    fn as_ref(&self) -> &str {
543        self.as_str()
544    }
545}
546
547impl<const N: usize> AsMut<str> for FStr<N> {
548    fn as_mut(&mut self) -> &mut str {
549        self.as_mut_str()
550    }
551}
552
553impl<const N: usize> AsRef<[u8]> for FStr<N> {
554    fn as_ref(&self) -> &[u8] {
555        self.as_bytes()
556    }
557}
558
559impl<const N: usize> From<FStr<N>> for [u8; N] {
560    fn from(value: FStr<N>) -> Self {
561        value.into_inner()
562    }
563}
564
565impl<const N: usize> str::FromStr for FStr<N> {
566    type Err = LengthError;
567
568    fn from_str(s: &str) -> Result<Self, Self::Err> {
569        Self::try_from_str(s)
570    }
571}
572
573impl<const N: usize> TryFrom<[u8; N]> for FStr<N> {
574    type Error = str::Utf8Error;
575
576    fn try_from(value: [u8; N]) -> Result<Self, Self::Error> {
577        Self::from_inner(value)
578    }
579}
580
581impl<const N: usize> TryFrom<&[u8; N]> for FStr<N> {
582    type Error = str::Utf8Error;
583
584    fn try_from(value: &[u8; N]) -> Result<Self, Self::Error> {
585        Self::from_inner(*value)
586    }
587}
588
589impl<const N: usize> TryFrom<&[u8]> for FStr<N> {
590    type Error = FromSliceError;
591
592    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
593        Self::try_from_slice(value)
594    }
595}
596
597/// A cursor-like writer structure returned by [`FStr::writer_at`].
598///
599/// See the `FStr::writer_at` documentation for the detailed behavior of this type's [`fmt::Write`]
600/// implementation.
601///
602/// # Examples
603///
604/// ```rust
605/// # use fstr::FStr;
606/// use core::fmt::Write as _;
607///
608/// let mut buffer = FStr::<20>::from_ascii_filler(b'.');
609/// assert_eq!(buffer, "....................");
610///
611/// let mut cursor = buffer.writer_at(0);
612/// assert_eq!(cursor.position(), 0);
613/// assert_eq!(&cursor.get_ref()[..], "....................");
614///
615/// write!(cursor, "gentle")?;
616/// assert_eq!(cursor.position(), 6);
617/// assert_eq!(&cursor.get_ref()[..], "gentle..............");
618///
619/// write!(cursor, " flamingo")?;
620/// assert_eq!(cursor.position(), 15);
621/// assert_eq!(&cursor.get_ref()[..], "gentle flamingo.....");
622///
623/// assert_eq!(
624///     cursor.get_ref().split_at(cursor.position()),
625///     ("gentle flamingo", ".....")
626/// );
627///
628/// assert_eq!(buffer, "gentle flamingo.....");
629/// # Ok::<_, core::fmt::Error>(())
630/// ```
631#[derive(Debug)]
632pub struct Cursor<T> {
633    inner: T,
634    pos: usize,
635}
636
637impl<T> Cursor<T> {
638    /// Gets a reference to the underlying value in this cursor.
639    pub fn get_ref(&self) -> &T {
640        &self.inner
641    }
642
643    // no get_mut() because unmanaged mutation may invalidate self.pos
644
645    /// Returns the current position of this cursor.
646    pub fn position(&self) -> usize {
647        self.pos
648    }
649}
650
651impl<T: AsRef<str>> Cursor<T> {
652    /// Creates a new cursor at the specified index.
653    fn with_position(pos: usize, inner: T) -> Option<Self> {
654        match inner.as_ref().is_char_boundary(pos) {
655            true => Some(Self { inner, pos }),
656            false => None,
657        }
658    }
659}
660
661impl<T: AsMut<str>> fmt::Write for Cursor<T> {
662    fn write_str(&mut self, s: &str) -> fmt::Result {
663        match self.inner.as_mut().get_mut(self.pos..(self.pos + s.len())) {
664            Some(written) => {
665                // SAFETY: ok because `written` and `s` are valid string slices of the same length
666                unsafe { written.as_bytes_mut() }.copy_from_slice(s.as_bytes());
667                self.pos += written.len();
668                Ok(())
669            }
670            None => Err(fmt::Error),
671        }
672    }
673}
674
675/// An error converting to [`FStr<N>`] from a slice having a different length than `N`.
676#[derive(Copy, Eq, PartialEq, Clone, Debug)]
677pub struct LengthError {
678    actual: usize,
679    expected: usize,
680}
681
682impl fmt::Display for LengthError {
683    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
684        write!(
685            f,
686            "invalid byte length of {} (expected: {})",
687            self.actual, self.expected
688        )
689    }
690}
691
692impl error::Error for LengthError {}
693
694/// An error converting to [`FStr<N>`] from a byte slice.
695#[derive(Copy, Eq, PartialEq, Clone, Debug)]
696pub struct FromSliceError {
697    kind: FromSliceErrorKind,
698}
699
700#[derive(Copy, Eq, PartialEq, Clone, Debug)]
701enum FromSliceErrorKind {
702    Length(LengthError),
703    Utf8(str::Utf8Error),
704}
705
706impl fmt::Display for FromSliceError {
707    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
708        use FromSliceErrorKind::{Length, Utf8};
709        match self.kind {
710            Length(source) => write!(f, "could not convert slice to FStr: {}", source),
711            Utf8(source) => write!(f, "could not convert slice to FStr: {}", source),
712        }
713    }
714}
715
716impl error::Error for FromSliceError {
717    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
718        match &self.kind {
719            FromSliceErrorKind::Length(source) => Some(source),
720            FromSliceErrorKind::Utf8(source) => Some(source),
721        }
722    }
723}
724
725#[inline(always)]
726const fn is_utf8_char_boundary(byte: u8) -> bool {
727    (byte as i8) >= -0x40 // test continuation byte (`0b10xx_xxxx`)
728}
729
730#[cfg(feature = "alloc")]
731mod with_string {
732    use alloc::{borrow::ToOwned as _, string::String};
733
734    use super::{FStr, LengthError};
735
736    impl<const N: usize> From<FStr<N>> for String {
737        fn from(value: FStr<N>) -> Self {
738            value.as_str().to_owned()
739        }
740    }
741
742    impl<const N: usize> TryFrom<String> for FStr<N> {
743        type Error = LengthError;
744
745        fn try_from(value: String) -> Result<Self, Self::Error> {
746            value.parse()
747        }
748    }
749
750    impl<const N: usize> PartialEq<String> for FStr<N> {
751        fn eq(&self, other: &String) -> bool {
752            self.as_str().eq(other)
753        }
754    }
755
756    impl<const N: usize> PartialEq<FStr<N>> for String {
757        fn eq(&self, other: &FStr<N>) -> bool {
758            self.eq(other.as_str())
759        }
760    }
761}
762
763#[cfg(test)]
764mod tests {
765    use super::FStr;
766
767    /// Tests `PartialEq` implementations.
768    #[test]
769    fn eq() {
770        let x = FStr::from_inner(*b"hello").unwrap();
771
772        assert_eq!(x, x);
773        assert_eq!(&x, &x);
774        assert_eq!(x, FStr::from_inner(*b"hello").unwrap());
775        assert_eq!(FStr::from_inner(*b"hello").unwrap(), x);
776        assert_eq!(&x, &FStr::from_inner(*b"hello").unwrap());
777        assert_eq!(&FStr::from_inner(*b"hello").unwrap(), &x);
778
779        assert_eq!(x, "hello");
780        assert_eq!("hello", x);
781        assert_eq!(&x, "hello");
782        assert_eq!("hello", &x);
783        assert_eq!(&x[..], "hello");
784        assert_eq!("hello", &x[..]);
785        assert_eq!(&x as &str, "hello");
786        assert_eq!("hello", &x as &str);
787
788        assert_ne!(x, FStr::from_inner(*b"world").unwrap());
789        assert_ne!(FStr::from_inner(*b"world").unwrap(), x);
790        assert_ne!(&x, &FStr::from_inner(*b"world").unwrap());
791        assert_ne!(&FStr::from_inner(*b"world").unwrap(), &x);
792
793        assert_ne!(x, "world");
794        assert_ne!("world", x);
795        assert_ne!(&x, "world");
796        assert_ne!("world", &x);
797        assert_ne!(&x[..], "world");
798        assert_ne!("world", &x[..]);
799        assert_ne!(&x as &str, "world");
800        assert_ne!("world", &x as &str);
801
802        #[cfg(feature = "alloc")]
803        {
804            use alloc::{borrow::ToOwned as _, string::String, string::ToString as _};
805
806            assert_eq!(x, String::from("hello"));
807            assert_eq!(String::from("hello"), x);
808
809            assert_eq!(String::from(x), String::from("hello"));
810            assert_eq!(String::from("hello"), String::from(x));
811
812            assert_ne!(x, String::from("world"));
813            assert_ne!(String::from("world"), x);
814
815            assert_ne!(String::from(x), String::from("world"));
816            assert_ne!(String::from("world"), String::from(x));
817
818            assert_eq!(x.to_owned(), String::from("hello"));
819            assert_eq!(String::from("hello"), x.to_owned());
820            assert_eq!(x.to_string(), String::from("hello"));
821            assert_eq!(String::from("hello"), x.to_string());
822        }
823    }
824
825    /// Tests `from_str_lossy()` against edge cases.
826    #[test]
827    fn from_str_lossy_edge() {
828        assert!(FStr::<0>::from_str_lossy("", b' ').is_empty());
829        assert!(FStr::<0>::from_str_lossy("pizza", b' ').is_empty());
830        assert!(FStr::<0>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' ').is_empty());
831
832        assert_eq!(FStr::<1>::from_str_lossy("", b' '), " ");
833        assert_eq!(FStr::<1>::from_str_lossy("pizza", b' '), "p");
834        assert_eq!(FStr::<1>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), " ");
835
836        assert_eq!(FStr::<2>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "  ");
837        assert_eq!(FStr::<3>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "   ");
838        assert_eq!(FStr::<4>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "πŸ₯Ή");
839        assert_eq!(FStr::<5>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "πŸ₯Ή ");
840        assert_eq!(FStr::<6>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "πŸ₯Ή  ");
841        assert_eq!(FStr::<7>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "πŸ₯Ή   ");
842        assert_eq!(FStr::<8>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "πŸ₯ΉπŸ₯Ή");
843        assert_eq!(FStr::<9>::from_str_lossy("πŸ₯ΉπŸ₯Ή", b' '), "πŸ₯ΉπŸ₯Ή ");
844    }
845
846    /// Tests `FromStr` implementation.
847    #[test]
848    fn from_str() {
849        assert!("ceremony".parse::<FStr<4>>().is_err());
850        assert!("strategy".parse::<FStr<12>>().is_err());
851        assert!("parallel".parse::<FStr<8>>().is_ok());
852        assert_eq!("parallel".parse::<FStr<8>>().unwrap(), "parallel");
853
854        assert!("πŸ˜‚".parse::<FStr<2>>().is_err());
855        assert!("πŸ˜‚".parse::<FStr<6>>().is_err());
856        assert!("πŸ˜‚".parse::<FStr<4>>().is_ok());
857        assert_eq!("πŸ˜‚".parse::<FStr<4>>().unwrap(), "πŸ˜‚");
858    }
859
860    /// Tests `TryFrom<[u8; N]>` and `TryFrom<&[u8; N]>` implementations.
861    #[test]
862    fn try_from_array() {
863        assert!(FStr::try_from(b"memory").is_ok());
864        assert!(FStr::try_from(*b"resort").is_ok());
865
866        assert!(FStr::try_from(&[0xff; 8]).is_err());
867        assert!(FStr::try_from([0xff; 8]).is_err());
868    }
869
870    /// Tests `TryFrom<&[u8]>` implementation.
871    #[test]
872    fn try_from_slice() {
873        assert!(FStr::<4>::try_from(b"memory".as_slice()).is_err());
874        assert!(FStr::<6>::try_from(b"memory".as_slice()).is_ok());
875        assert!(FStr::<8>::try_from(b"memory".as_slice()).is_err());
876
877        assert!(FStr::<7>::try_from([0xff; 8].as_slice()).is_err());
878        assert!(FStr::<8>::try_from([0xff; 8].as_slice()).is_err());
879        assert!(FStr::<9>::try_from([0xff; 8].as_slice()).is_err());
880    }
881
882    /// Tests `fmt::Write` implementation of `Cursor`.
883    #[test]
884    fn write_str() {
885        use core::fmt::Write as _;
886
887        let mut a = FStr::<5>::from_ascii_filler(b' ');
888        assert!(write!(a.writer_at(0), "vanilla").is_err());
889        assert_eq!(a, "     ");
890
891        let mut b = FStr::<7>::from_ascii_filler(b' ');
892        assert!(write!(b.writer_at(0), "vanilla").is_ok());
893        assert_eq!(b, "vanilla");
894
895        let mut c = FStr::<9>::from_ascii_filler(b' ');
896        assert!(write!(c.writer_at(0), "vanilla").is_ok());
897        assert_eq!(c, "vanilla  ");
898
899        let mut d = FStr::<16>::from_ascii_filler(b'.');
900        assert!(write!(d.writer_at(0), "πŸ˜‚πŸ€ͺπŸ˜±πŸ‘»").is_ok());
901        assert_eq!(d, "πŸ˜‚πŸ€ͺπŸ˜±πŸ‘»");
902        assert!(write!(d.writer_at(0), "πŸ”₯").is_ok());
903        assert_eq!(d, "πŸ”₯πŸ€ͺπŸ˜±πŸ‘»");
904        assert!(write!(d.writer_at(0), "πŸ₯ΊπŸ˜­").is_ok());
905        assert_eq!(d, "πŸ₯ΊπŸ˜­πŸ˜±πŸ‘»");
906        assert!(write!(d.writer_at(0), ".").is_err());
907        assert_eq!(d, "πŸ₯ΊπŸ˜­πŸ˜±πŸ‘»");
908
909        let mut e = FStr::<12>::from_ascii_filler(b' ');
910        assert!(write!(e.writer_at(0), "{:04}/{:04}", 42, 334).is_ok());
911        assert_eq!(e, "0042/0334   ");
912
913        let mut w = e.writer_at(0);
914        assert!(write!(w, "{:02x}", 123).is_ok());
915        assert!(write!(w, "-{:04x}", 345).is_ok());
916        assert!(write!(w, "-{:04x}", 567).is_ok());
917        assert!(write!(w, "-{:04x}", 789).is_err());
918        assert_eq!(e, "7b-0159-0237");
919
920        assert!(write!(FStr::<0>::default().writer_at(0), "").is_ok());
921        assert!(write!(FStr::<0>::default().writer_at(0), " ").is_err());
922
923        assert!(write!(FStr::<5>::default().writer_at(5), "").is_ok());
924        assert!(write!(FStr::<5>::default().writer_at(5), " ").is_err());
925    }
926
927    #[test]
928    #[should_panic]
929    fn writer_at_index_middle_of_a_char() {
930        FStr::<8>::from_str_lossy("πŸ™", b' ').writer_at(1);
931    }
932
933    #[test]
934    #[should_panic]
935    fn writer_at_index_beyond_end() {
936        FStr::<5>::default().writer_at(7);
937    }
938
939    /// Tests `Hash` and `Borrow` implementations using `HashSet`.
940    #[cfg(feature = "std")]
941    #[test]
942    fn hash_borrow() {
943        use std::collections::HashSet;
944
945        let mut s = HashSet::new();
946        s.insert(FStr::from_inner(*b"crisis").unwrap());
947        s.insert(FStr::from_inner(*b"eating").unwrap());
948        s.insert(FStr::from_inner(*b"lucent").unwrap());
949
950        assert!(s.contains("crisis"));
951        assert!(s.contains("eating"));
952        assert!(s.contains("lucent"));
953        assert!(!s.contains("system"));
954        assert!(!s.contains("unless"));
955        assert!(!s.contains("yellow"));
956
957        assert!(s.contains(&FStr::from_inner(*b"crisis").unwrap()));
958        assert!(s.contains(&FStr::from_inner(*b"eating").unwrap()));
959        assert!(s.contains(&FStr::from_inner(*b"lucent").unwrap()));
960        assert!(!s.contains(&FStr::from_inner(*b"system").unwrap()));
961        assert!(!s.contains(&FStr::from_inner(*b"unless").unwrap()));
962        assert!(!s.contains(&FStr::from_inner(*b"yellow").unwrap()));
963    }
964
965    /// Tests `fmt::Display` implementation.
966    #[cfg(feature = "alloc")]
967    #[test]
968    fn display_fmt() {
969        use alloc::format;
970
971        let a = FStr::from_inner(*b"you").unwrap();
972
973        assert_eq!(format!("{}", a), "you");
974        assert_eq!(format!("{:5}", a), "you  ");
975        assert_eq!(format!("{:<6}", a), "you   ");
976        assert_eq!(format!("{:-<7}", a), "you----");
977        assert_eq!(format!("{:>8}", a), "     you");
978        assert_eq!(format!("{:^9}", a), "   you   ");
979
980        let b = FStr::from_inner(*b"junior").unwrap();
981
982        assert_eq!(format!("{}", b), "junior");
983        assert_eq!(format!("{:.3}", b), "jun");
984        assert_eq!(format!("{:5.3}", b), "jun  ");
985        assert_eq!(format!("{:<6.3}", b), "jun   ");
986        assert_eq!(format!("{:-<7.3}", b), "jun----");
987        assert_eq!(format!("{:>8.3}", b), "     jun");
988        assert_eq!(format!("{:^9.3}", b), "   jun   ");
989    }
990
991    /// Tests `from_fmt()`.
992    #[test]
993    fn from_fmt() {
994        let args = format_args!("vanilla");
995        assert!(FStr::<5>::from_fmt(args, b' ').is_err());
996        assert_eq!(FStr::<7>::from_fmt(args, b' ').unwrap(), "vanilla");
997        assert_eq!(FStr::<9>::from_fmt(args, b' ').unwrap(), "vanilla  ");
998
999        assert_eq!(
1000            FStr::<20>::from_fmt(format_args!("{:^6}", "πŸ˜‚πŸ€ͺπŸ˜±πŸ‘»"), b'.').unwrap(),
1001            " πŸ˜‚πŸ€ͺπŸ˜±πŸ‘» .."
1002        );
1003
1004        assert_eq!(
1005            FStr::<12>::from_fmt(format_args!("{:04}/{:04}", 42, 334), b'\0').unwrap(),
1006            "0042/0334\0\0\0"
1007        );
1008
1009        assert_eq!(FStr::<0>::from_fmt(format_args!(""), b' ').unwrap(), "");
1010        assert!(FStr::<0>::from_fmt(format_args!(" "), b' ').is_err());
1011    }
1012}
1013
1014#[cfg(feature = "serde")]
1015mod with_serde {
1016    use super::{FStr, fmt};
1017    use serde::{Deserializer, Serializer, de};
1018
1019    impl<const N: usize> serde::Serialize for FStr<N> {
1020        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1021            serializer.serialize_str(self.as_str())
1022        }
1023    }
1024
1025    impl<'de, const N: usize> serde::Deserialize<'de> for FStr<N> {
1026        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1027            deserializer.deserialize_str(VisitorImpl)
1028        }
1029    }
1030
1031    struct VisitorImpl<const N: usize>;
1032
1033    impl<const N: usize> de::Visitor<'_> for VisitorImpl<N> {
1034        type Value = FStr<N>;
1035
1036        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1037            write!(formatter, "a fixed-length string")
1038        }
1039
1040        fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
1041            value.parse().map_err(de::Error::custom)
1042        }
1043
1044        fn visit_bytes<E: de::Error>(self, value: &[u8]) -> Result<Self::Value, E> {
1045            if let Ok(inner) = value.try_into() {
1046                if let Ok(t) = FStr::from_inner(inner) {
1047                    return Ok(t);
1048                }
1049            }
1050
1051            Err(de::Error::invalid_value(
1052                de::Unexpected::Bytes(value),
1053                &self,
1054            ))
1055        }
1056    }
1057
1058    #[test]
1059    fn ser_de() {
1060        use serde_test::Token;
1061
1062        let x = FStr::from_inner(*b"helloworld").unwrap();
1063        serde_test::assert_tokens(&x, &[Token::Str("helloworld")]);
1064        serde_test::assert_de_tokens(&x, &[Token::Bytes(b"helloworld")]);
1065
1066        let y = "πŸ˜‚πŸ€ͺπŸ˜±πŸ‘»".parse::<FStr<16>>().unwrap();
1067        serde_test::assert_tokens(&y, &[Token::Str("πŸ˜‚πŸ€ͺπŸ˜±πŸ‘»")]);
1068        serde_test::assert_de_tokens(
1069            &y,
1070            &[Token::Bytes(&[
1071                240, 159, 152, 130, 240, 159, 164, 170, 240, 159, 152, 177, 240, 159, 145, 187,
1072            ])],
1073        );
1074
1075        serde_test::assert_de_tokens_error::<FStr<5>>(
1076            &[Token::Str("helloworld")],
1077            "invalid byte length of 10 (expected: 5)",
1078        );
1079        serde_test::assert_de_tokens_error::<FStr<5>>(
1080            &[Token::Bytes(b"helloworld")],
1081            "invalid value: byte array, expected a fixed-length string",
1082        );
1083        serde_test::assert_de_tokens_error::<FStr<5>>(
1084            &[Token::Bytes(&[b'h', b'e', b'l', b'l', 240])],
1085            "invalid value: byte array, expected a fixed-length string",
1086        );
1087    }
1088}