abi_stable/sabi_types/
nul_str.rs

1//! A nul-terminated string,which is just a pointer to the string data,
2//! it doesn't know the length of the string.
3
4#[cfg(test)]
5mod tests;
6
7use crate::std_types::RStr;
8
9use const_panic::{concat_assert, concat_panic};
10
11use std::{
12    cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd},
13    fmt::{self, Debug, Display},
14    marker::PhantomData,
15    ptr::NonNull,
16};
17
18/// A utf8 nul-terminated immutable borrowed string.
19///
20/// For the purpose of passing `NulStr`s to C,
21/// this has the same ABI as a `std::ptr::NonNull<u8>`,
22/// and an `Option<NulStr<'_>>` has the same ABI as `*const u8`.
23///
24/// # Safety
25///
26/// `NulStr` has these safety requirement:
27/// - the string must be valid to read for the `'a` lifetime
28/// - the string must be utf8 encoded
29/// - the string must be nul terminated
30/// - the string must not be mutated while this is alive
31/// (the same semantics as `&` references)
32///
33/// # Example
34///
35/// ### Passing to extern function
36///
37/// You can pass `NulStr` to C functions expecting a nul-terminated string.
38///
39/// ```rust
40/// use abi_stable::sabi_types::NulStr;
41///
42/// extern "C" {
43///     // the signature in the C side is `uint64_t add_digits(const char*)`
44///     fn add_digits(_: NulStr<'_>) -> u64;
45/// }
46/// # #[export_name = "add_digits"]
47/// # pub extern "C" fn add_digits___(str: NulStr<'_>) -> u64 {
48/// #    str.to_str().bytes()
49/// #    .filter_map(|x|{
50/// #        match x {
51/// #            b'0'..=b'9' => Some(u64::from(x - b'0')),
52/// #            _ => None,
53/// #        }
54/// #    })
55/// #    .sum()
56/// # }
57///
58/// # fn main() {
59/// const FOO: NulStr<'_> = NulStr::from_str("1.2.3\0");
60/// const BAR: NulStr<'_> = NulStr::from_str("12|34\0");
61/// const QUX: NulStr<'_> = NulStr::from_str("123_abcd_45\0");
62///
63/// assert_eq!(unsafe { add_digits(FOO) }, 6);
64/// assert_eq!(unsafe { add_digits(BAR) }, 10);
65/// assert_eq!(unsafe { add_digits(QUX) }, 15);
66/// # }
67/// ```
68#[repr(transparent)]
69#[derive(Copy, Clone, StableAbi)]
70pub struct NulStr<'a> {
71    ptr: NonNull<u8>,
72    _marker: PhantomData<&'a u8>,
73}
74
75unsafe impl Sync for NulStr<'_> {}
76unsafe impl Send for NulStr<'_> {}
77
78impl NulStr<'static> {
79    /// An empty string.
80    pub const EMPTY: Self = NulStr::from_str("\0");
81}
82
83impl<'a> NulStr<'a> {
84    /// Constructs an `NulStr` from a string slice.
85    ///
86    /// # Correctness
87    ///
88    /// If the string contains interior nuls,
89    /// the first nul will be considered the string terminator.
90    ///
91    /// # Panics
92    ///
93    /// This panics when the string does not end with `'\0'`.
94    ///
95    /// # Example
96    ///
97    /// ```rust
98    /// use abi_stable::sabi_types::NulStr;
99    ///
100    /// const FOO: NulStr<'_> = NulStr::from_str("foo\0");
101    /// // `NulStr`s can be compared with `str`s
102    /// assert_eq!(FOO, "foo");
103    ///
104    /// const BAR: NulStr<'_> = NulStr::from_str("bar\0");
105    /// assert_eq!(BAR, "bar");
106    ///
107    /// const HEWWO: NulStr<'_> = NulStr::from_str("Hello, world!\0");
108    /// assert_eq!(HEWWO, "Hello, world!");
109    ///
110    /// const TRUNCATED: NulStr<'_> = NulStr::from_str("baz\0world!\0");
111    /// assert_eq!(TRUNCATED, "baz");
112    ///
113    /// ```
114    pub const fn from_str(str: &'a str) -> Self {
115        let this = Self {
116            ptr: crate::utils::ref_as_nonnull(str).cast::<u8>(),
117            _marker: PhantomData,
118        };
119
120        let last_byte = str.as_bytes()[str.len() - 1] as usize;
121        concat_assert! {
122            last_byte == 0,
123            "expected a nul terminator, found:",
124            last_byte,
125        };
126        this
127    }
128
129    /// Constructs an NulStr from a string slice.
130    ///
131    /// # Errors
132    ///
133    /// This returns a [`NulStrError::NoNulTerminator`] when the string does not end
134    /// with `'\0'`.
135    ///
136    /// This returns a [`NulStrError::InnerNul`] when the string contains a
137    /// `'\0'` before the `'\0'` terminator.
138    ///
139    /// # Example
140    ///
141    /// ```rust
142    /// use abi_stable::sabi_types::{NulStr, NulStrError};
143    ///
144    /// // `NulStr`s can be compared with `str`s
145    /// assert_eq!(NulStr::try_from_str("hello\0").unwrap(), "hello");
146    ///
147    /// assert_eq!(
148    ///     NulStr::try_from_str("hello\0world\0"),
149    ///     Err(NulStrError::InnerNul { pos: 5 }),
150    /// );
151    ///
152    /// ```
153    ///
154    /// [`NulStrError::InnerNul`]: enum.NulStrError.html#variant.InnerNul
155    /// [`NulStrError::NoNulTerminator`]: enum.NulStrError.html#variant.NoNulTerminator
156    pub const fn try_from_str(string: &'a str) -> Result<Self, NulStrError> {
157        let mut i = 0;
158        let mut bytes = string.as_bytes();
159
160        bytes = match bytes {
161            [rem @ .., 0] => rem,
162            _ => return Err(NulStrError::NoNulTerminator),
163        };
164
165        while let [b, ref rem @ ..] = *bytes {
166            if b == 0 {
167                return Err(NulStrError::InnerNul { pos: i });
168            }
169            i += 1;
170            bytes = rem;
171        }
172
173        unsafe { Ok(NulStr::from_ptr(string.as_ptr())) }
174    }
175
176    #[doc(hidden)]
177    #[track_caller]
178    pub const fn __try_from_str_unwrapping(s: &'a str) -> Self {
179        match Self::try_from_str(s) {
180            Ok(x) => x,
181            Err(NulStrError::InnerNul { pos }) => {
182                concat_panic!("encountered inner nul byte at position: ", pos)
183            }
184            Err(NulStrError::NoNulTerminator) => concat_panic!("found no nul-terminator"),
185        }
186    }
187
188    /// Constructs an NulStr from a pointer.
189    ///
190    /// # Safety
191    ///
192    /// [The same as the type-level safety docs](#safety)
193    ///
194    /// # Correctness
195    ///
196    /// If the string contains interior nuls,
197    /// the first nul will be considered the string terminator.
198    ///
199    /// # Example
200    ///
201    /// ```rust
202    /// use abi_stable::sabi_types::NulStr;
203    ///
204    /// const FOO: NulStr<'_> = unsafe { NulStr::from_ptr("foo\0".as_ptr()) };
205    /// assert_eq!(FOO, "foo");
206    ///
207    /// const BAR: NulStr<'_> = unsafe { NulStr::from_ptr("bar\0".as_ptr()) };
208    /// assert_eq!(BAR, "bar");
209    ///
210    /// const HEWWO: NulStr<'_> = unsafe { NulStr::from_ptr("Hello, world!\0".as_ptr()) };
211    /// assert_eq!(HEWWO, "Hello, world!");
212    ///
213    /// const TRUNCATED: NulStr<'_> = unsafe { NulStr::from_ptr("baz\0world!\0".as_ptr()) };
214    /// assert_eq!(TRUNCATED, "baz");
215    ///
216    /// ```
217    pub const unsafe fn from_ptr(ptr: *const u8) -> Self {
218        Self {
219            ptr: unsafe { NonNull::new_unchecked(ptr as *mut u8) },
220            _marker: PhantomData,
221        }
222    }
223
224    /// Gets a pointer to the start of this nul-terminated string.
225    ///
226    /// # Example
227    ///
228    /// ```rust
229    /// use abi_stable::sabi_types::NulStr;
230    ///
231    /// let foo_str = "foo\0";
232    /// let foo = NulStr::from_str(foo_str);
233    /// assert_eq!(foo.as_ptr(), foo_str.as_ptr());
234    ///
235    /// ```
236    pub const fn as_ptr(self) -> *const u8 {
237        self.ptr.as_ptr()
238    }
239
240    /// Converts this `NulStr<'a>` to a `&'a str`,including the nul byte.
241    ///
242    /// # Performance
243    ///
244    /// This conversion requires traversing through the entire string to
245    /// find the nul byte.
246    ///
247    /// # Example
248    ///
249    /// ```rust
250    /// use abi_stable::sabi_types::NulStr;
251    ///
252    /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
253    /// let foo: &str = FOO.to_str_with_nul();
254    /// assert_eq!(&foo[..3], "foo");
255    /// assert_eq!(&foo[4..], "bar\0");
256    ///
257    /// ```
258    pub fn to_str_with_nul(&self) -> &'a str {
259        unsafe {
260            let bytes = std::ffi::CStr::from_ptr(self.ptr.as_ptr() as *const _).to_bytes_with_nul();
261            std::str::from_utf8_unchecked(bytes)
262        }
263    }
264
265    /// Computes the length of the string, NOT including the nul terminator.
266    #[cfg(feature = "rust_1_64")]
267    const fn compute_length(self) -> usize {
268        let start: *const u8 = self.ptr.as_ptr();
269        let mut ptr = start;
270        let mut len = 0;
271        unsafe {
272            while *ptr != 0 {
273                ptr = ptr.offset(1);
274                len += 1;
275            }
276            len
277        }
278    }
279
280    /// Converts this `NulStr<'a>` to a `&'a str`,including the nul byte.
281    ///
282    /// # Performance
283    ///
284    /// To make this function const-callable,
285    /// this uses a potentially less efficient approach than
286    /// [`to_str_with_nul`](Self::to_str_with_nul).
287    ///
288    /// This conversion requires traversing through the entire string to
289    /// find the nul byte.
290    ///
291    /// # Example
292    ///
293    /// ```rust
294    /// use abi_stable::sabi_types::NulStr;
295    ///
296    /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
297    /// const FOO_S: &str = FOO.const_to_str_with_nul();
298    /// assert_eq!(&FOO_S[..3], "foo");
299    /// assert_eq!(&FOO_S[4..], "bar\0");
300    ///
301    /// ```
302    #[cfg(feature = "rust_1_64")]
303    #[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
304    pub const fn const_to_str_with_nul(&self) -> &'a str {
305        unsafe {
306            let len = self.compute_length();
307            std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.as_ptr(), len + 1))
308        }
309    }
310
311    /// Converts this `NulStr<'a>` to a `RStr<'a>`,including the nul byte.
312    ///
313    /// # Performance
314    ///
315    /// This conversion requires traversing through the entire string to
316    /// find the nul byte.
317    ///
318    /// # Example
319    ///
320    /// ```rust
321    /// use abi_stable::sabi_types::NulStr;
322    /// use abi_stable::std_types::RStr;
323    ///
324    /// const BAZ: NulStr<'_> = NulStr::from_str("baz qux\0");
325    /// let baz: RStr<'_> = BAZ.to_rstr_with_nul();
326    /// assert_eq!(&baz[..3], "baz");
327    /// assert_eq!(&baz[4..], "qux\0");
328    ///
329    /// ```
330    pub fn to_rstr_with_nul(&self) -> RStr<'a> {
331        self.to_str_with_nul().into()
332    }
333
334    /// Converts this `NulStr<'a>` to a `&'a str`,not including the nul byte.
335    ///
336    /// # Performance
337    ///
338    /// This conversion requires traversing through the entire string to
339    /// find the nul byte.
340    ///
341    /// # Example
342    ///
343    /// ```rust
344    /// use abi_stable::sabi_types::NulStr;
345    ///
346    /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
347    /// let foo: &str = FOO.to_str();
348    /// assert_eq!(&foo[..3], "foo");
349    /// assert_eq!(&foo[4..], "bar");
350    ///
351    /// ```
352    pub fn to_str(self) -> &'a str {
353        unsafe {
354            let bytes = std::ffi::CStr::from_ptr(self.ptr.as_ptr() as *const _).to_bytes();
355            std::str::from_utf8_unchecked(bytes)
356        }
357    }
358
359    /// Converts this `NulStr<'a>` to a `&'a str`,not including the nul byte.
360    ///
361    /// # Performance
362    ///
363    /// To make this function const-callable,
364    /// this uses a potentially less efficient approach than [`to_str`](Self::to_str).
365    ///
366    /// This conversion requires traversing through the entire string to
367    /// find the nul byte.
368    ///
369    /// # Example
370    ///
371    /// ```rust
372    /// use abi_stable::sabi_types::NulStr;
373    ///
374    /// const FOO: NulStr<'_> = NulStr::from_str("foo bar\0");
375    /// const FOO_S: &str = FOO.const_to_str();
376    /// assert_eq!(&FOO_S[..3], "foo");
377    /// assert_eq!(&FOO_S[4..], "bar");
378    ///
379    /// ```
380    #[cfg(feature = "rust_1_64")]
381    #[cfg_attr(feature = "docsrs", doc(cfg(feature = "rust_1_64")))]
382    pub const fn const_to_str(self) -> &'a str {
383        unsafe {
384            let len = self.compute_length();
385            std::str::from_utf8_unchecked(std::slice::from_raw_parts(self.as_ptr(), len))
386        }
387    }
388
389    /// Converts this `NulStr<'a>` to a `RStr<'a>`,not including the nul byte.
390    ///
391    /// # Performance
392    ///
393    /// This conversion requires traversing through the entire string to
394    /// find the nul byte.
395    ///
396    /// # Example
397    ///
398    /// ```rust
399    /// use abi_stable::sabi_types::NulStr;
400    /// use abi_stable::std_types::RStr;
401    ///
402    /// const BAZ: NulStr<'_> = NulStr::from_str("baz qux\0");
403    /// let baz: RStr<'_> = BAZ.to_rstr();
404    /// assert_eq!(&baz[..3], "baz");
405    /// assert_eq!(&baz[4..], "qux");
406    ///
407    /// ```
408    pub fn to_rstr(self) -> RStr<'a> {
409        self.to_str().into()
410    }
411}
412
413impl<'a> PartialEq<NulStr<'a>> for &str {
414    fn eq(&self, other: &NulStr<'a>) -> bool {
415        self.as_ptr() == other.as_ptr() || *self == other.to_str()
416    }
417}
418
419impl<'a> PartialEq<&str> for NulStr<'a> {
420    fn eq(&self, other: &&str) -> bool {
421        self.as_ptr() == other.as_ptr() || self.to_str() == *other
422    }
423}
424
425impl<'a> PartialEq<NulStr<'a>> for str {
426    fn eq(&self, other: &NulStr<'a>) -> bool {
427        self.as_ptr() == other.as_ptr() || self == other.to_str()
428    }
429}
430
431impl<'a> PartialEq<str> for NulStr<'a> {
432    fn eq(&self, other: &str) -> bool {
433        self.as_ptr() == other.as_ptr() || self.to_str() == other
434    }
435}
436
437impl<'a> PartialEq for NulStr<'a> {
438    fn eq(&self, other: &Self) -> bool {
439        self.ptr == other.ptr || self.to_str() == other.to_str()
440    }
441}
442
443impl<'a> Eq for NulStr<'a> {}
444
445impl<'a> PartialOrd for NulStr<'a> {
446    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
447        Some(self.cmp(other))
448    }
449}
450
451impl<'a> Ord for NulStr<'a> {
452    fn cmp(&self, other: &Self) -> Ordering {
453        if self.ptr == other.ptr {
454            Ordering::Equal
455        } else {
456            self.to_str().cmp(other.to_str())
457        }
458    }
459}
460
461impl Default for NulStr<'_> {
462    fn default() -> Self {
463        NulStr::EMPTY
464    }
465}
466
467impl Display for NulStr<'_> {
468    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469        Display::fmt(self.to_str(), f)
470    }
471}
472
473impl Debug for NulStr<'_> {
474    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
475        Debug::fmt(self.to_str(), f)
476    }
477}
478
479/// Error from trying to convert a `&str` to a [`NulStr`]
480///
481/// [`NulStr`]: ./struct.NulStr.html
482#[derive(Debug, PartialEq, Eq, Clone)]
483#[non_exhaustive]
484pub enum NulStrError {
485    /// When the string has a `'\0'` before the end.
486    InnerNul {
487        /// the position of the first '\0' character.
488        pos: usize,
489    },
490    /// When the string doesn't end with `'\0'`
491    NoNulTerminator,
492}
493
494impl Display for NulStrError {
495    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496        match *self {
497            Self::InnerNul { pos } => {
498                write!(f, "there is an internal nul at the {} byte offset", pos)
499            }
500            Self::NoNulTerminator => f.write_str("there is no nul terminator in the string"),
501        }
502    }
503}
504
505impl std::error::Error for NulStrError {}