flexstr/
cstr.rs

1use alloc::{borrow::Cow, ffi::CString, rc::Rc, sync::Arc};
2use core::{
3    ffi::{CStr, FromBytesWithNulError},
4    str::FromStr,
5};
6
7use crate::flex::{
8    FlexStr, ImmutableBytes, RefCounted, RefCountedMut, partial_eq_impl, ref_counted_mut_impl,
9};
10
11pub use flexstr_support::InteriorNulError;
12use flexstr_support::StringToFromBytes;
13use inline_flexstr::{InlineFlexStr, TooLongOrNulError};
14
15/// Local `CStr` type (NOTE: This can't be shared between threads)
16pub type LocalCStr = FlexStr<'static, CStr, Rc<CStr>>;
17
18/// Shared `CStr` type
19pub type SharedCStr = FlexStr<'static, CStr, Arc<CStr>>;
20
21/// Local `CStr` type that can optionally hold borrows (NOTE: This can't be shared between threads)
22pub type LocalCStrRef<'s> = FlexStr<'s, CStr, Rc<CStr>>;
23
24/// Shared `CStr` type that can optionally hold borrows
25pub type SharedCStrRef<'s> = FlexStr<'s, CStr, Arc<CStr>>;
26
27// NOTE: This one is a bit different because CString is just a Box<[u8]>. Instead of equal size,
28// we should be at most one machine word larger.
29const _: () = assert!(
30    size_of::<Option<LocalCStr>>() <= size_of::<CString>() + size_of::<usize>(),
31    "Option<LocalCStr> must be less than or equal to the size of CString plus one machine word"
32);
33const _: () = assert!(
34    size_of::<Option<SharedCStr>>() <= size_of::<CString>() + size_of::<usize>(),
35    "Option<SharedCStr> must be less than or equal to the size of CString plus one machine word"
36);
37
38// *** FlexStr ***
39
40impl<'s, R: RefCounted<CStr>> FlexStr<'s, CStr, R> {
41    fn from_bytes_without_nul(bytes: &'s [u8]) -> Self {
42        // NOTE: This will scan the string for interior NUL bytes _twice_. Consider optionally
43        // making InlineFlexStr::try_from_bytes_without_nul unsafe and using it conditionally here.
44        match InlineFlexStr::try_from_bytes_with_or_without_nul(bytes) {
45            Ok(inline) => FlexStr::from_inline(inline),
46            // Finally, fallback to creating a new CString so nul zero is appended
47            Err(TooLongOrNulError::TooLong(_)) => FlexStr::from_owned(
48                #[cfg(feature = "safe")]
49                // PANIC SAFETY: We already tested for interior NUL bytes
50                CString::new(bytes).expect("Unexpected interior NUL byte"),
51                #[cfg(not(feature = "safe"))]
52                // SAFETY: We already tested for interior NUL bytes
53                unsafe {
54                    CString::from_vec_unchecked(bytes.into())
55                },
56            ),
57            // PANIC SAFETY: We already tested for interior NUL bytes from the function that called this one
58            Err(TooLongOrNulError::NulError(e)) => {
59                unreachable!("Interior NUL byte found at position {}", e.position)
60            }
61        }
62    }
63
64    /// Attempt to create a CStr from borrowed bytes with or without a trailing NUL byte.
65    pub fn try_from_bytes_with_or_without_nul(bytes: &'s [u8]) -> Result<Self, InteriorNulError> {
66        match CStr::from_bytes_with_nul(bytes) {
67            // If it is already a valid CStr, then just borrow it
68            Ok(cstr) => Ok(FlexStr::from_borrowed(cstr)),
69            // Otherwise try and inline it, adding a nul zero
70            Err(FromBytesWithNulError::NotNulTerminated) => Ok(Self::from_bytes_without_nul(bytes)),
71            Err(FromBytesWithNulError::InteriorNul { position }) => {
72                Err(InteriorNulError { position })
73            }
74        }
75    }
76
77    /// Borrow the CStr as bytes with a trailing NUL byte
78    #[inline]
79    pub fn as_bytes_with_nul(&self) -> &[u8] {
80        self.as_raw_bytes()
81    }
82}
83
84// *** ImmutableBytes ***
85
86impl ImmutableBytes for CStr {}
87
88// *** RefCountedMut ***
89
90ref_counted_mut_impl!(CStr);
91
92// *** From<CString> ***
93
94// NOTE: Cannot be implemented generically because of impl<T> From<T> for T
95impl<'s, R: RefCounted<CStr>> From<CString> for FlexStr<'s, CStr, R> {
96    fn from(s: CString) -> Self {
97        FlexStr::from_owned(s)
98    }
99}
100
101// *** TryFrom for FlexStr ***
102
103impl<'s, R: RefCounted<CStr>> TryFrom<&'s str> for FlexStr<'s, CStr, R> {
104    type Error = InteriorNulError;
105
106    #[inline]
107    fn try_from(s: &'s str) -> Result<Self, Self::Error> {
108        FlexStr::try_from_bytes_with_or_without_nul(s.as_bytes())
109    }
110}
111
112impl<'s, R: RefCounted<CStr>> TryFrom<&'s [u8]> for FlexStr<'s, CStr, R> {
113    type Error = InteriorNulError;
114
115    #[inline]
116    fn try_from(bytes: &'s [u8]) -> Result<Self, Self::Error> {
117        FlexStr::try_from_bytes_with_or_without_nul(bytes)
118    }
119}
120
121// *** PartialEq ***
122
123partial_eq_impl!(CStr, CStr);
124partial_eq_impl!(&CStr, CStr);
125partial_eq_impl!(CString, CStr);
126partial_eq_impl!(Cow<'s, CStr>, CStr);
127
128// *** AsRef ***
129
130impl<'s, S: ?Sized + StringToFromBytes, R: RefCounted<S>> AsRef<CStr> for FlexStr<'s, S, R>
131where
132    S: AsRef<CStr>,
133{
134    fn as_ref(&self) -> &CStr {
135        self.as_ref_type().as_ref()
136    }
137}
138
139// *** FromStr ***
140
141impl<R: RefCounted<CStr>> FromStr for FlexStr<'static, CStr, R> {
142    type Err = InteriorNulError;
143
144    fn from_str(s: &str) -> Result<Self, Self::Err> {
145        FlexStr::try_from_bytes_with_or_without_nul(s.as_bytes()).map(FlexStr::into_owned)
146    }
147}