str_array/
cstr.rs

1use core::slice;
2use core::{
3    ffi::CStr,
4    fmt::{self, Debug},
5    num::NonZeroU8,
6    ops::Deref,
7};
8
9use crate::error::{CStrLenError, InteriorNulError};
10use NulByte::Nul;
11
12/// A byte that must always be 0.
13#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
14#[repr(u8)]
15enum NulByte {
16    Nul = 0,
17}
18
19const fn count_bytes(val: &CStr) -> usize {
20    // Avoid bumping the MSRV and stay `const` by using `unsafe`.
21    let mut p = val.as_ptr();
22    let mut other_len = 0;
23    while unsafe { *p } != 0 {
24        other_len += 1;
25        p = unsafe { p.add(1) };
26    }
27    other_len
28}
29
30/// Fixed-size [`CStr`], backed by an array.
31///
32/// The length `N` is the number of bytes in the string _not_ including the nul terminator.
33///
34/// Because it has a fixed size, it can be put directly in a `static` and all
35/// casting operations are constant-time.
36// Note: As of writing, `&mut CStr` is not sound to construct.
37#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
38#[repr(C)]
39pub struct CStrArray<const N: usize> {
40    data: [NonZeroU8; N],
41    nul: NulByte,
42}
43
44impl<const N: usize> CStrArray<N> {
45    /// Builds a `StrArray<N>` from `val`.
46    ///
47    /// This returns an `Err` if `val.len() != N`.
48    ///
49    /// If `val` is a literal or `const`, consider using [`cstr_array!`]
50    /// instead, which always builds a `CStrArray` with the correct `N`
51    /// by checking the length at compile time.
52    ///
53    /// [`cstr_array!`]: crate::cstr_array
54    pub const fn new(val: &CStr) -> Result<Self, CStrLenError<N>> {
55        let other_len = count_bytes(val);
56        if other_len != N {
57            return Err(CStrLenError { src_len: other_len });
58        }
59        // SAFETY: `val` is checked to point to `N` non-nul bytes followed by a nul.
60        Ok(unsafe { Self::new_unchecked(val) })
61    }
62
63    /// Builds a `StrArray<N>` from `val` without a size check.
64    ///
65    /// # Safety
66    ///
67    /// `val.count_bytes() == N` or else behavior is undefined.
68    pub const unsafe fn new_unchecked(val: &CStr) -> Self {
69        // SAFETY: `val` is known to point to `N` non-nul bytes followed by a nul.
70        CStrArray {
71            data: unsafe { *val.as_ptr().cast() },
72            nul: Nul,
73        }
74    }
75
76    /// Constructs a `CStrArray<N>` from an array with its byte contents.
77    ///
78    /// Note that `bytes` should _not_ include the nul terminator -
79    /// it is appended automatically by this method.
80    ///
81    /// If `val` is a literal or `const`, consider using [`cstr_array!`]
82    /// instead, which checks for the presence of a nul at compile time.
83    ///
84    /// [`cstr_array!`]: crate::cstr_array
85    pub const fn from_bytes_without_nul(bytes: &[u8; N]) -> Result<Self, InteriorNulError> {
86        // Avoid bumping the MSRV and stay `const` by using a manual loop.
87        let mut i = 0;
88        while i < N {
89            if bytes[i] == 0 {
90                return Err(InteriorNulError { position: i });
91            }
92            i += 1;
93        }
94        // SAFETY: `bytes` has been checked to contain no interior nul bytes
95        Ok(unsafe { Self::from_bytes_without_nul_unchecked(bytes) })
96    }
97
98    /// Constructs a `CStrArray<N>` from an array with its byte contents and no checks.
99    ///
100    /// Note that this does _not_ include the nul terminator -
101    /// it is appended automatically.
102    ///
103    /// # Safety
104    ///
105    /// `bytes` must not have any 0 (nul) bytes.
106    pub const unsafe fn from_bytes_without_nul_unchecked(
107        bytes: &[u8; N],
108    ) -> Self {
109        // SAFETY: `bytes` does not contain any 0 values as promised by the caller.
110        let nonzero: &[NonZeroU8; N] = unsafe { &*bytes.as_ptr().cast() };
111        Self {
112            data: *nonzero,
113            nul: Nul,
114        }
115    }
116
117    /// Returns the fixed length.
118    #[allow(clippy::len_without_is_empty)]
119    pub const fn len(&self) -> usize {
120        N
121    }
122
123    /// Borrows this `CStrArray` as a `&CStr`.
124    ///
125    /// This is called by `Deref` automatically.
126    pub const fn as_c_str(&self) -> &CStr {
127        // SAFETY:
128        // - The first `N` bytes of `self` (`data` field) are kept non-nul.
129        // - The last byte of `self` is always a nul byte.
130        unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) }
131    }
132
133    /// Converts this C string to a byte array reference.
134    ///
135    /// The returned slice will not contain the trailing nul terminator
136    /// that this C string has.
137    pub const fn as_bytes(&self) -> &[u8; N] {
138        // SAFETY: `[NonZeroU8; N]` has the same layout as `[u8; N]` and cannot be mutated through a reference.
139        unsafe { &*self.data.as_ptr().cast() }
140    }
141
142    /// Converts this C string to a byte slice containing the trailing 0 byte.
143    ///
144    /// The length of the slice is `N + 1`.
145    pub const fn as_bytes_with_nul(&self) -> &[u8] {
146        // SAFETY:
147        // - `Self` uses the `repr(C)` layout algorithm.
148        // - The total size of `Self` is `N + 1`
149        // - All bytes in `Self` are initialized.
150        unsafe { slice::from_raw_parts(self as *const _ as *const u8, N + 1) }
151    }
152
153    /// Consumes `self` into its underlying array.
154    pub const fn into_bytes(self) -> [u8; N] {
155        *self.as_bytes()
156    }
157
158    /// Returns the fixed length.
159    ///
160    /// This uses the same name as [`CStr::count_bytes`] to prevent
161    /// it from being called with `Deref`.
162    #[deprecated = "use `len`"]
163    pub const fn count_bytes(&self) -> usize {
164        self.len()
165    }
166
167    /// Converts this C string to a byte slice.
168    ///
169    /// This uses the same name as [`CStr::to_bytes`] to prevent
170    /// it from being called with `Deref`.
171    #[deprecated = "use `as_bytes`"]
172    pub const fn to_bytes(&self) -> &[u8] {
173        self.as_bytes()
174    }
175
176    /// Converts this C string to a byte slice.
177    ///
178    /// This uses the same name as [`CStr::to_bytes_with_nul`] to prevent
179    /// it from being called with `Deref`.
180    #[deprecated = "use `as_bytes_with_nul`"]
181    pub const fn to_bytes_with_nul(&self) -> &[u8] {
182        self.as_bytes_with_nul()
183    }
184}
185
186impl CStrArray<0> {
187    /// Returns an empty `CStrArray`.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// # use str_array::CStrArray;
193    /// let s = CStrArray::empty();
194    /// assert!(s.is_empty());
195    /// ```
196    pub const fn empty() -> Self {
197        Self { data: [], nul: Nul }
198    }
199}
200
201impl<const N: usize> Debug for CStrArray<N> {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        <CStr as Debug>::fmt(self.as_c_str(), f)
204    }
205}
206
207impl<const N: usize> Deref for CStrArray<N> {
208    type Target = CStr;
209
210    fn deref(&self) -> &Self::Target {
211        self.as_c_str()
212    }
213}
214
215impl<const N: usize> AsRef<CStr> for CStrArray<N> {
216    fn as_ref(&self) -> &CStr {
217        self.as_c_str()
218    }
219}
220
221impl<const N: usize> TryFrom<&CStr> for CStrArray<N> {
222    type Error = CStrLenError<N>;
223
224    fn try_from(val: &CStr) -> Result<Self, Self::Error> {
225        Self::new(val)
226    }
227}
228
229#[cfg(feature = "alloc")]
230mod alloc {
231    use super::*;
232    use crate::*;
233
234    impl<const N: usize> TryFrom<Box<CStr>> for CStrArray<N> {
235        type Error = CStrLenError<N>;
236
237        fn try_from(val: Box<CStr>) -> Result<Self, Self::Error> {
238            Self::new(&val)
239        }
240    }
241
242    impl<const N: usize> TryFrom<Rc<CStr>> for CStrArray<N> {
243        type Error = CStrLenError<N>;
244
245        fn try_from(val: Rc<CStr>) -> Result<Self, Self::Error> {
246            Self::new(&val)
247        }
248    }
249
250    impl<const N: usize> TryFrom<Arc<CStr>> for CStrArray<N> {
251        type Error = CStrLenError<N>;
252
253        fn try_from(val: Arc<CStr>) -> Result<Self, Self::Error> {
254            Self::new(&val)
255        }
256    }
257
258    impl<const N: usize> TryFrom<CString> for CStrArray<N> {
259        type Error = CStrLenError<N>;
260
261        fn try_from(val: CString) -> Result<Self, Self::Error> {
262            Self::new(&val)
263        }
264    }
265
266    impl<const N: usize> PartialEq<CString> for CStrArray<N> {
267        fn eq(&self, other: &CString) -> bool {
268            self.as_c_str() == other.as_c_str()
269        }
270    }
271
272    impl<const N: usize> PartialEq<CStrArray<N>> for CString {
273        fn eq(&self, other: &CStrArray<N>) -> bool {
274            self.as_c_str() == other.as_c_str()
275        }
276    }
277}
278
279impl<const N: usize> PartialEq<CStr> for CStrArray<N> {
280    fn eq(&self, other: &CStr) -> bool {
281        self.as_c_str() == other
282    }
283}
284
285impl<const N: usize> PartialEq<CStrArray<N>> for CStr {
286    fn eq(&self, other: &CStrArray<N>) -> bool {
287        self == other.as_c_str()
288    }
289}
290
291impl<const N: usize> PartialEq<&CStr> for CStrArray<N> {
292    fn eq(&self, other: &&CStr) -> bool {
293        self.as_c_str() == *other
294    }
295}
296
297impl<const N: usize> PartialEq<CStrArray<N>> for &CStr {
298    fn eq(&self, other: &CStrArray<N>) -> bool {
299        *self == other.as_c_str()
300    }
301}
302
303/// Internal utility struct used by `cstr_array!`.
304pub struct CStrArrayBytes<T>(pub T);
305
306/// Builds a `CStrArray<N>` from `bytes` without nul, panicking on failure.
307pub const fn build_cstr<const N: usize>(bytes: &[u8]) -> CStrArray<N> {
308    let as_array: &[u8; N] = if bytes.len() == N {
309        // SAFETY: `self.0.len() == N`
310        unsafe { &*bytes.as_ptr().cast() }
311    } else {
312        CStrLenError::<N> {
313            src_len: bytes.len(),
314        }
315        .const_panic()
316    };
317    match CStrArray::from_bytes_without_nul(as_array) {
318        Ok(x) => x,
319        Err(e) => e.const_panic(),
320    }
321}
322
323impl<'a, const N: usize> CStrArrayBytes<&'a [u8; N]> {
324    /// Unsizes the byte array
325    pub const fn get(self) -> &'a [u8] {
326        self.0
327    }
328}
329
330impl<'a> CStrArrayBytes<&'a [u8]> {
331    /// Copies the slice
332    pub const fn get(self) -> &'a [u8] {
333        self.0
334    }
335}
336
337impl<'a> CStrArrayBytes<&'a str> {
338    /// Gets the bytes of the `str`
339    pub const fn get(self) -> &'a [u8] {
340        self.0.as_bytes()
341    }
342}
343
344impl<'a> CStrArrayBytes<&'a CStr> {
345    /// Gets the bytes of the `CStr`, counting up to the nul terminator
346    pub const fn get(self) -> &'a [u8] {
347        // Using `CStr::to_bytes` bumps the MSRV higher than desired.
348        // SAFETY: `count_bytes` returns the number of bytes that are present before the nul character.
349        unsafe { slice::from_raw_parts(self.0.as_ptr().cast(), count_bytes(self.0)) }
350    }
351}
352
353/// Builds [`CStrArray`] from constant strings of various types.
354///
355/// This macro can construct a `CStrArray<N>` from a constant expression of any of these input types:
356///
357/// - `&CStr`
358/// - `&str`
359/// - `&[u8]`
360/// - `&[u8; N]`
361///
362/// This performs the necessary nul presence checks at compile time.
363///
364/// # Examples
365///
366/// Pass a `const` expression of `&CStr` to build a `CStrArray` with the same length.
367///
368/// ```
369/// # use core::ffi::CStr;
370/// # use str_array::cstr_array;
371/// let x = cstr_array!(c"Buzz");
372/// assert_eq!(x.len(), 4);
373///
374/// const NAME: &CStr = c"Sally";
375/// let y = cstr_array!(NAME);
376/// assert_eq!(y, c"Sally");
377/// ```
378///
379/// Pass a `const` expression of `&str` or `&[u8]` to build a `CStrArray` with the same length.
380/// A nul terminator is appended automatically.
381///
382/// ```
383/// # use core::ffi::CStr;
384/// # use str_array::cstr_array;
385/// assert_eq!(cstr_array!("Buzz"), cstr_array!(b"Buzz"));
386/// ```
387///
388/// It's a compile-time failure to invoke `cstr_array!` with a nul inside the string.
389///
390/// ```compile_fail
391/// # use core::ffi::CStr;
392/// # use str_array::cstr_array;
393/// _ = cstr_array!("nul is appended by the macro; don't include it\0");
394/// ```
395///
396/// ```compile_fail
397/// # use core::ffi::CStr;
398/// # use str_array::cstr_array;
399/// _ = cstr_array!(b"nul\0in the middle");
400/// ```
401///
402/// Define `static` or `const` items by eliding the type.
403///
404/// The length of the `CStrArray` uses the length of the assigned string.
405/// Note that the assignment expression currently is evaluated twice,
406/// but this should have no effect due to it being in `const`.
407///
408/// ```
409/// # use core::ptr::addr_of;
410/// # use str_array::{cstr_array, CStrArray};
411/// const BYTE_ARRAY: [u8; 12] = *b"direct array";
412///
413/// cstr_array! {
414///     static STATIC = c"static";
415///     static mut STATIC_MUT = c"static_mut";
416///     const CONST = c"constant";
417///
418///     static FROM_STR = concat!("checked", " for ", "nul");
419///     static FROM_BYTES_REF = b"also checked for nul";
420///     static FROM_BYTES_ARRAY = &BYTE_ARRAY;  // doesn't construct directly from array
421/// }
422///
423/// assert_eq!(STATIC, c"static");
424/// assert_eq!(unsafe { &*addr_of!(STATIC_MUT) }, c"static_mut");
425/// assert_eq!(CONST, c"constant");
426///
427/// assert_eq!(FROM_STR, c"checked for nul");
428/// assert_eq!(FROM_BYTES_REF, c"also checked for nul");
429/// assert_eq!(FROM_BYTES_ARRAY, *c"direct array");
430/// ```
431// TODO: Support plain string literals too (requires a nul check).
432// TODO: maybe just write `static STATIC = cstr!("static")`
433#[macro_export]
434macro_rules! cstr_array {
435    // TODO: same caveats as str_array
436    (@impl item
437        ($([$attr:meta])*)
438        ($($item_kind:tt)*)
439        $name:ident = $val:expr; $($rest:tt)*
440    ) => {
441        $(#[$attr])* $($item_kind)* $name: $crate::CStrArray<{ $crate::__internal::CStrArrayBytes($val).get().len() }> =
442            $crate::__internal::build_cstr($crate::__internal::CStrArrayBytes($val).get());
443        $crate::cstr_array!($($rest)*)
444    };
445    ($(#[$attr:meta])* static mut $($rest:tt)*) => {
446        $crate::cstr_array!(@impl item ($([$attr])*) (static mut) $($rest)*);
447    };
448    ($(#[$attr:meta])* static $($rest:tt)*) => {
449        $crate::cstr_array!(@impl item ($([$attr])*) (static) $($rest)*);
450    };
451    ($(#[$attr:meta])* const $($rest:tt)*) => {
452        $crate::cstr_array!(@impl item ($([$attr])*) (const) $($rest)*);
453    };
454    ($val:expr) => {{
455        const VAL: &[u8] = $crate::__internal::CStrArrayBytes($val).get();
456        const ARRAY: $crate::CStrArray<{ VAL.len() }> = $crate::__internal::build_cstr(VAL);
457        ARRAY
458    }};
459    () => {};
460}
461
462#[cfg(test)]
463macro_rules! cstr {
464    ($x:literal) => {
465        match core::ffi::CStr::from_bytes_with_nul(concat!($x, "\0").as_bytes()) {
466            Ok(x) => x,
467            Err(_) => panic!(),
468        }
469    };
470}
471
472#[cfg(test)]
473mod tests {
474    use super::*;
475    use ::alloc::format;
476
477    #[test]
478    fn test_new() {
479        let cstr = cstr!("hello");
480        let cstr_array = CStrArray::<5>::new(cstr).unwrap();
481        assert_eq!(cstr_array.as_c_str(), cstr);
482        assert_eq!(cstr_array.len(), 5);
483        assert!(!cstr_array.is_empty());
484
485        let cstr_long = cstr!("hellos");
486        let err = CStrArray::<5>::new(cstr_long).unwrap_err();
487        assert_eq!(err.src_len, 6);
488
489        let cstr_short = cstr!("hell");
490        let err = CStrArray::<5>::new(cstr_short).unwrap_err();
491        assert_eq!(err.src_len, 4);
492    }
493
494    #[test]
495    fn test_from_bytes_without_nul() {
496        let bytes = b"hello";
497        let cstr_array = CStrArray::from_bytes_without_nul(bytes).unwrap();
498        assert_eq!(cstr_array.as_bytes(), bytes);
499
500        let bytes_with_nul = b"he\0llo";
501        let err = CStrArray::from_bytes_without_nul(bytes_with_nul).unwrap_err();
502        assert_eq!(err.position, 2);
503    }
504
505    #[test]
506    fn test_empty() {
507        let empty = CStrArray::<0>::empty();
508        assert!(empty.is_empty());
509        assert_eq!(empty.len(), 0);
510        assert_eq!(empty.as_c_str(), <&CStr>::default());
511    }
512
513    #[test]
514    fn test_debug() {
515        let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
516        let s = format!("{:?}", cstr_array);
517        assert_eq!(s, r#""hello""#);
518    }
519
520    #[test]
521    fn test_as_bytes_with_nul() {
522        let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
523        assert_eq!(cstr_array.as_bytes_with_nul(), b"hello\0");
524    }
525
526    #[test]
527    fn test_into_bytes() {
528        let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
529        assert_eq!(cstr_array.into_bytes(), *b"hello");
530    }
531
532    #[test]
533    fn test_try_from_cstr() {
534        let cstr = cstr!("hello");
535        let cstr_array = CStrArray::<5>::try_from(cstr).unwrap();
536        assert_eq!(cstr_array.as_c_str(), cstr);
537
538        let cstr_long = cstr!("hellos");
539        let err = CStrArray::<5>::try_from(cstr_long).unwrap_err();
540        assert_eq!(err.src_len, 6);
541    }
542
543    #[test]
544    fn test_partial_eq() {
545        let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
546        let cstr = cstr!("hello");
547        assert_eq!(&cstr_array, cstr);
548        assert_eq!(cstr, &cstr_array);
549        assert_eq!(&cstr_array, &cstr);
550        assert_eq!(&cstr, &cstr_array);
551    }
552
553    #[test]
554    fn test_macro() {
555        let cstr_array = cstr_array!(cstr!("hello"));
556        assert_eq!(cstr_array.len(), 5);
557        assert_eq!(cstr_array, cstr!("hello"));
558
559        let str_array = cstr_array!("hello");
560        assert_eq!(str_array.len(), 5);
561        assert_eq!(str_array, cstr!("hello"));
562
563        let bytes_array = cstr_array!(b"hello");
564        assert_eq!(bytes_array.len(), 5);
565        assert_eq!(bytes_array, cstr!("hello"));
566    }
567
568    #[test]
569    #[deny(non_upper_case_globals)]
570    fn test_macro_items() {
571        cstr_array! {
572            static STATIC = cstr!("hello");
573            const CONST = "world";
574        }
575        assert_eq!(STATIC.len(), 5);
576        assert_eq!(STATIC, cstr!("hello"));
577        assert_eq!(CONST.len(), 5);
578        assert_eq!(CONST, cstr!("world"));
579    }
580}
581
582#[cfg(all(test, feature = "alloc"))]
583mod alloc_tests {
584    use super::*;
585    use ::alloc::{boxed::Box, ffi::CString, rc::Rc, sync::Arc};
586
587    #[test]
588    fn test_try_from_alloc() {
589        let cstr = cstr!("hello");
590        let cstring = CString::new("hello").unwrap();
591
592        let from_box = CStrArray::<5>::try_from(Box::from(cstr)).unwrap();
593        assert_eq!(from_box, cstr);
594
595        let from_rc = CStrArray::<5>::try_from(Rc::from(cstr)).unwrap();
596        assert_eq!(from_rc, cstr);
597
598        let from_arc = CStrArray::<5>::try_from(Arc::from(cstr)).unwrap();
599        assert_eq!(from_arc, cstr);
600
601        let from_cstring = CStrArray::<5>::try_from(cstring.clone()).unwrap();
602        assert_eq!(from_cstring, cstr);
603    }
604
605    #[test]
606    fn test_partial_eq_alloc() {
607        let cstr_array = CStrArray::from_bytes_without_nul(b"hello").unwrap();
608        let cstring = CString::new("hello").unwrap();
609        assert_eq!(cstr_array, *cstring);
610        assert_eq!(*cstring, cstr_array);
611    }
612}