spacetimedb_data_structures/nstr.rs
1//! Provides UTF-8 strings of compile-time-known lengths.
2//!
3//! The strings can be constructed with `nstr!(string_literal)`
4//! producing a type `NStr<N>` where `const N: usize`.
5//! An example would be `nstr!("spacetime"): NStr<9>`.
6
7use core::{fmt, ops::Deref, str};
8use std::ops::DerefMut;
9
10/// A UTF-8 string of known length `N`.
11///
12/// The notion of length is that of the standard library's `String` type.
13/// That is, the length is in bytes, not chars or graphemes.
14///
15/// It holds that `size_of::<NStr<N>>() == N`
16/// but `&NStr<N>` is always word sized.
17#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct NStr<const N: usize>([u8; N]);
19
20// This function exists due to macro-privacy interactions.
21#[doc(hidden)]
22pub const fn __nstr<const N: usize>(s: &str) -> NStr<N> {
23 // Ensure that the string was `N` in length.
24 // This has no runtime cost as the `nstr!` macro forces compile-time eval.
25 if N != s.len() {
26 panic!("string does not match claimed length");
27 };
28
29 // Convert the string to bytes.
30 // Need to use `while` to do this at compile time.
31 let src = s.as_bytes();
32 let mut dst = [0; N];
33 let mut i = 0;
34 while i < N {
35 dst[i] = src[i];
36 i += 1;
37 }
38
39 NStr(dst)
40}
41
42/// Constructs an `NStr<N>` given a string literal of `N` bytes in length.
43///
44/// # Example
45///
46/// ```
47/// use spacetimedb_data_structures::{nstr, nstr::NStr};
48/// let s: NStr<3> = nstr!("foo");
49/// assert_eq!(&*s, "foo");
50/// assert_eq!(s.len(), 3);
51/// assert_eq!(format!("{s}"), "foo");
52/// assert_eq!(format!("{s:?}"), "foo");
53/// ```
54#[macro_export]
55macro_rules! nstr {
56 ($lit:literal) => {
57 $crate::nstr::__nstr::<{ $lit.len() }>($lit).into()
58 };
59}
60
61impl<const N: usize> Deref for NStr<N> {
62 type Target = str;
63
64 #[inline]
65 fn deref(&self) -> &Self::Target {
66 // SAFETY: An `NStr<N>` can only be made through `__nstr(..)`
67 // and which receives an `&str` which is valid UTF-8.
68 unsafe { str::from_utf8_unchecked(&self.0) }
69 }
70}
71
72impl<const N: usize> DerefMut for NStr<N> {
73 #[inline]
74 fn deref_mut(&mut self) -> &mut Self::Target {
75 // SAFETY: An `NStr<N>` can only be made through `__nstr(..)`
76 // and which receives an `&str` which is valid UTF-8.
77 unsafe { str::from_utf8_unchecked_mut(&mut self.0) }
78 }
79}
80
81impl<const N: usize> fmt::Debug for NStr<N> {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 f.write_str(self.deref())
84 }
85}
86
87impl<const N: usize> fmt::Display for NStr<N> {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.write_str(self.deref())
90 }
91}