cstr8/
slice.rs

1#[cfg(feature = "std")]
2use std::{ffi::OsStr, path::Path};
3
4use core::{
5    cmp,
6    ffi::{CStr, FromBytesWithNulError},
7    fmt,
8    ops::{Deref, Index},
9    slice::SliceIndex,
10    str::{self, Utf8Error},
11};
12
13/// String slice which is guaranteed UTF-8 and nul-terminated.
14///
15/// This dereferences to `str` *without the nul terminator*. If you want to
16/// use the nul terminator, use [`as_c_str`](Self::as_c_str) instead.
17#[repr(transparent)]
18#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct CStr8 {
20    raw: str,
21}
22
23impl Deref for CStr8 {
24    type Target = str;
25
26    fn deref(&self) -> &str {
27        self.as_str()
28    }
29}
30
31impl Default for &'_ CStr8 {
32    fn default() -> Self {
33        cstr8!("")
34    }
35}
36
37impl fmt::Debug for CStr8 {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        self.as_str().fmt(f)
40    }
41}
42
43impl fmt::Display for CStr8 {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        self.as_str().fmt(f)
46    }
47}
48
49impl AsRef<str> for CStr8 {
50    fn as_ref(&self) -> &str {
51        self.as_str()
52    }
53}
54
55impl AsRef<CStr> for CStr8 {
56    fn as_ref(&self) -> &CStr {
57        self.as_c_str()
58    }
59}
60
61impl AsRef<[u8]> for CStr8 {
62    fn as_ref(&self) -> &[u8] {
63        self.as_bytes()
64    }
65}
66
67impl PartialEq<str> for CStr8 {
68    fn eq(&self, other: &str) -> bool {
69        self.as_str() == other
70    }
71}
72
73impl PartialEq<CStr8> for str {
74    fn eq(&self, other: &CStr8) -> bool {
75        self == other.as_str()
76    }
77}
78
79impl PartialOrd<str> for CStr8 {
80    fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
81        self.as_str().partial_cmp(other)
82    }
83}
84
85impl PartialOrd<CStr8> for str {
86    fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
87        self.partial_cmp(other.as_str())
88    }
89}
90
91impl PartialEq<CStr> for CStr8 {
92    fn eq(&self, other: &CStr) -> bool {
93        self.as_c_str() == other
94    }
95}
96
97impl PartialEq<CStr8> for CStr {
98    fn eq(&self, other: &CStr8) -> bool {
99        self == other.as_c_str()
100    }
101}
102
103impl PartialOrd<CStr> for CStr8 {
104    fn partial_cmp(&self, other: &CStr) -> Option<cmp::Ordering> {
105        self.as_c_str().partial_cmp(other)
106    }
107}
108
109impl PartialOrd<CStr8> for CStr {
110    fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
111        self.partial_cmp(other.as_c_str())
112    }
113}
114
115impl<'a> TryFrom<&'a CStr> for &'a CStr8 {
116    type Error = CStr8Error;
117
118    fn try_from(s: &'a CStr) -> Result<&'a CStr8, CStr8Error> {
119        CStr8::from_utf8_with_nul(s.to_bytes_with_nul())
120    }
121}
122
123#[cfg(feature = "alloc")]
124mod alloc_impls {
125    use {
126        crate::{CStr8, CString8},
127        alloc::{
128            borrow::{Cow, ToOwned},
129            boxed::Box,
130            ffi::CString,
131            rc::Rc,
132            string::String,
133            sync::Arc,
134        },
135        core::ffi::CStr,
136    };
137
138    // We only provide From impls matching CStr, not str. This helps avoid some
139    // "does it include the nul terminator" confusion around inferred .into()s.
140
141    impl From<&CStr8> for Arc<CStr8> {
142        fn from(s: &CStr8) -> Arc<CStr8> {
143            let arc = Arc::<[u8]>::from(s.as_bytes_with_nul());
144            // SAFETY: This is how you spell a transmute of Arc's pointee type.
145            unsafe { Arc::from_raw(Arc::into_raw(arc) as *const CStr8) }
146        }
147    }
148
149    impl From<&CStr8> for Arc<CStr> {
150        fn from(s: &CStr8) -> Arc<CStr> {
151            s.as_c_str().into()
152        }
153    }
154
155    impl From<&CStr8> for Arc<str> {
156        fn from(s: &CStr8) -> Arc<str> {
157            s.as_str().into()
158        }
159    }
160
161    impl From<&CStr8> for Box<CStr8> {
162        fn from(s: &CStr8) -> Box<CStr8> {
163            let boxed = Box::<[u8]>::from(s.as_bytes_with_nul());
164            // SAFETY: This is how you spell a transmute of Box's pointee type.
165            unsafe { Box::from_raw(Box::into_raw(boxed) as *mut CStr8) }
166        }
167    }
168
169    impl From<&CStr8> for Box<CStr> {
170        fn from(s: &CStr8) -> Box<CStr> {
171            s.as_c_str().into()
172        }
173    }
174
175    impl From<&CStr8> for Box<str> {
176        fn from(s: &CStr8) -> Box<str> {
177            s.as_str().into()
178        }
179    }
180
181    impl<'a> From<&'a CStr8> for Cow<'a, CStr8> {
182        fn from(s: &'a CStr8) -> Cow<'a, CStr8> {
183            Cow::Borrowed(s)
184        }
185    }
186
187    impl<'a> From<&'a CStr8> for Cow<'a, CStr> {
188        fn from(s: &'a CStr8) -> Cow<'a, CStr> {
189            s.as_c_str().into()
190        }
191    }
192
193    impl<'a> From<&'a CStr8> for Cow<'a, str> {
194        fn from(s: &'a CStr8) -> Cow<'a, str> {
195            s.as_str().into()
196        }
197    }
198
199    impl From<&CStr8> for Rc<CStr8> {
200        fn from(s: &CStr8) -> Rc<CStr8> {
201            let rc = Rc::<[u8]>::from(s.as_bytes_with_nul());
202            // SAFETY: This is how you spell a transmute of Rc's pointee type.
203            unsafe { Rc::from_raw(Rc::into_raw(rc) as *const CStr8) }
204        }
205    }
206
207    impl From<&CStr8> for Rc<CStr> {
208        fn from(s: &CStr8) -> Rc<CStr> {
209            s.as_c_str().into()
210        }
211    }
212
213    impl From<&CStr8> for Rc<str> {
214        fn from(s: &CStr8) -> Rc<str> {
215            s.as_str().into()
216        }
217    }
218
219    impl From<&CStr8> for CString8 {
220        fn from(s: &CStr8) -> CString8 {
221            s.to_owned()
222        }
223    }
224
225    impl From<&CStr8> for CString {
226        fn from(s: &CStr8) -> CString {
227            s.as_c_str().into()
228        }
229    }
230
231    impl From<&CStr8> for String {
232        fn from(s: &CStr8) -> String {
233            s.as_str().into()
234        }
235    }
236
237    impl From<Cow<'_, CStr8>> for Box<CStr8> {
238        fn from(s: Cow<'_, CStr8>) -> Box<CStr8> {
239            match s {
240                Cow::Borrowed(s) => Box::from(s),
241                Cow::Owned(s) => Box::from(s),
242            }
243        }
244    }
245
246    impl PartialEq<String> for CStr8 {
247        fn eq(&self, other: &String) -> bool {
248            self.as_str() == other
249        }
250    }
251
252    impl PartialEq<CStr8> for String {
253        fn eq(&self, other: &CStr8) -> bool {
254            self == other.as_str()
255        }
256    }
257
258    impl ToOwned for CStr8 {
259        type Owned = CString8;
260
261        fn to_owned(&self) -> CString8 {
262            // SAFETY: The single nul terminator is maintained.
263            unsafe { CString8::from_vec_unchecked(self.as_bytes_with_nul().to_owned()) }
264        }
265
266        // fn clone_into(&self, target: &mut CString8) {
267        //     let mut b = mem::take(target).into_bytes_with_nul();
268        //     self.as_bytes_with_nul().clone_into(&mut b);
269        //     *target = unsafe { CString8::from_vec_unchecked(b) }
270        // }
271    }
272}
273
274#[cfg(feature = "std")]
275mod std_impls {
276    use {
277        crate::CStr8,
278        core::cmp,
279        std::{
280            ffi::{OsStr, OsString},
281            path::Path,
282        },
283    };
284
285    impl AsRef<OsStr> for CStr8 {
286        fn as_ref(&self) -> &OsStr {
287            self.as_str().as_ref()
288        }
289    }
290
291    impl AsRef<Path> for CStr8 {
292        fn as_ref(&self) -> &Path {
293            self.as_str().as_ref()
294        }
295    }
296
297    impl PartialEq<OsStr> for CStr8 {
298        fn eq(&self, other: &OsStr) -> bool {
299            self.as_str() == other
300        }
301    }
302
303    impl PartialEq<OsString> for &'_ CStr8 {
304        fn eq(&self, other: &OsString) -> bool {
305            self.as_str() == other
306        }
307    }
308
309    impl PartialEq<OsString> for CStr8 {
310        fn eq(&self, other: &OsString) -> bool {
311            self.as_str() == other
312        }
313    }
314
315    impl PartialEq<CStr8> for OsStr {
316        fn eq(&self, other: &CStr8) -> bool {
317            self == other.as_str()
318        }
319    }
320
321    impl PartialEq<CStr8> for OsString {
322        fn eq(&self, other: &CStr8) -> bool {
323            self == other.as_str()
324        }
325    }
326
327    impl PartialOrd<CStr8> for OsStr {
328        fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
329            self.partial_cmp(other.as_str())
330        }
331    }
332
333    impl PartialOrd<CStr8> for OsString {
334        fn partial_cmp(&self, other: &CStr8) -> Option<cmp::Ordering> {
335            self.partial_cmp(other.as_str())
336        }
337    }
338}
339
340impl<I> Index<I> for CStr8
341where
342    I: SliceIndex<str>,
343{
344    type Output = I::Output;
345
346    fn index(&self, index: I) -> &Self::Output {
347        &self.as_str()[index]
348    }
349}
350
351/// Explicit conversions.
352impl CStr8 {
353    /// Converts this to a normal string slice.
354    /// *This will not include the nul terminator*.
355    ///
356    /// You can also just use the generic prelude [`AsRef::as_ref`] instead.
357    pub const fn as_str(&self) -> &str {
358        match self.raw.as_bytes() {
359            [rest @ .., _nul] => unsafe { str::from_utf8_unchecked(rest) },
360            [] => unreachable!(),
361        }
362    }
363
364    /// Converts this to a normal C string.
365    /// *This will include the nul terminator*.
366    ///
367    /// You can also just use the generic prelude [`AsRef::as_ref`] instead.
368    pub const fn as_c_str(&self) -> &CStr {
369        unsafe { CStr::from_bytes_with_nul_unchecked(self.raw.as_bytes()) }
370    }
371
372    /// Converts this to a normal byte slice.
373    /// *This will not include the nul terminator*.
374    ///
375    /// You can also just use the generic prelude [`AsRef::as_ref`] instead.
376    pub const fn as_bytes(&self) -> &[u8] {
377        self.as_str().as_bytes()
378    }
379
380    /// Converts this to a normal byte slice.
381    /// *This will include the nul terminator*.
382    ///
383    /// Note that [`AsRef::as_ref`] *excludes* the nul terminator.
384    pub const fn as_bytes_with_nul(&self) -> &[u8] {
385        self.raw.as_bytes()
386    }
387
388    /// Converts this to a normal OS string slice.
389    /// *This will not include the nul terminator*.
390    ///
391    /// You can also just use the generic prelude [`AsRef::as_ref`] instead.
392    #[cfg(feature = "std")]
393    pub fn as_os_str(&self) -> &OsStr {
394        self.as_ref()
395    }
396
397    /// Converts this to a normal path slice.
398    /// *This will not include the nul terminator*.
399    ///
400    /// You can also just use the generic prelude [`AsRef::as_ref`] instead.
401    #[cfg(feature = "std")]
402    pub fn as_path(&self) -> &Path {
403        self.as_ref()
404    }
405
406    /// Converts this to a raw C string pointer.
407    ///
408    /// This method deliberately shadows `str::as_ptr` to ensure that for the
409    /// purpose of the aliasing model, the returned pointer will be valid for
410    /// the entire byte range, including the nul terminator.
411    pub const fn as_ptr(&self) -> *const u8 {
412        self.as_bytes_with_nul().as_ptr()
413    }
414}
415
416/// Constructors.
417impl CStr8 {
418    /// Asserts that the byte slice is valid UTF-8 and nul-terminated.
419    ///
420    /// # Examples
421    ///
422    /// Basic usage:
423    ///
424    /// ```rust
425    /// use cstr8::CStr8;
426    ///
427    /// // some bytes, in a vector
428    /// let sparkle_heart = vec![240, 159, 146, 150, 0];
429    ///
430    /// // We know these are valid UTF-8 and nul-terminated, so just unwrap.
431    /// let sparkle_heart = CStr8::from_utf8_with_nul(&sparkle_heart).unwrap();
432    ///
433    /// assert_eq!("💖", sparkle_heart);
434    /// ```
435    ///
436    /// Incorrect bytes:
437    ///
438    /// ```rust
439    /// use cstr8::CStr8;
440    ///
441    /// // Invalid UTF-8
442    /// let sparkle_heart = vec![1, 159, 146, 150, 0];
443    /// assert!(CStr8::from_utf8_with_nul(&sparkle_heart).is_err());
444    ///
445    /// // Missing nul terminator
446    /// let sparkle_heart = vec![240, 159, 146, 150];
447    /// assert!(CStr8::from_utf8_with_nul(&sparkle_heart).is_err());
448    ///
449    /// // Embedded nul terminator
450    /// let sparkle_heart = vec![0, 240, 159, 146, 150, 0];
451    /// assert!(CStr8::from_utf8_with_nul(&sparkle_heart).is_err());
452    /// ```
453    pub fn from_utf8_with_nul(v: &[u8]) -> Result<&CStr8, CStr8Error> {
454        let _ = str::from_utf8(v)?;
455        let _ = CStr::from_bytes_with_nul(v)?;
456        Ok(unsafe { CStr8::from_utf8_with_nul_unchecked(v) })
457    }
458
459    /// Asserts that the byte slice contains a nul-terminator and is valid UTF-8
460    /// up to that point.
461    ///
462    /// If the first byte is a nul character, this method will return an empty
463    /// `CStr8`. If multiple nul characters are present, the `CStr8` will end
464    /// at the first one.
465    ///
466    /// If the slice only has a single nul byte at the end, this method is
467    /// equivalent to [`CStr8::from_utf8_with_nul`].
468    ///
469    /// # Examples
470    ///
471    /// ```rust
472    /// use cstr8::CStr8;
473    ///
474    /// let mut buffer = [0u8; 16];
475    /// unsafe {
476    ///     // Here we might call an unsafe C function that writes a string
477    ///     // into the buffer.
478    ///     let buf_ptr = buffer.as_mut_ptr();
479    ///     buf_ptr.write_bytes(b'A', 8);
480    /// }
481    /// // Attempt to extract a nul-terminated string from the buffer.
482    /// let c_str = CStr8::from_utf8_until_nul(&buffer)?;
483    /// assert_eq!(c_str, "AAAAAAAA");
484    /// # Ok::<_, cstr8::CStr8Error>(())
485    /// ```
486    pub fn from_utf8_until_nul(v: &[u8]) -> Result<&CStr8, CStr8Error> {
487        let v = CStr::from_bytes_until_nul(v)
488            .map(CStr::to_bytes_with_nul)
489            .unwrap_or_default();
490        Self::from_utf8_with_nul(v)
491    }
492
493    /// Unsafely assumes that the byte slice is valid UTF-8 and nul-terminated.
494    ///
495    /// # Safety
496    ///
497    /// The provided bytes must be valid UTF-8, nul-terminated, and not contain
498    /// any interior nul bytes.
499    pub const unsafe fn from_utf8_with_nul_unchecked(v: &[u8]) -> &CStr8 {
500        &*(v as *const [u8] as *const CStr8)
501    }
502
503    /// Wraps a raw C string into a `CStr8`.
504    ///
505    /// # Safety
506    ///
507    /// The provided pointer must reference valid nul-terminated UTF-8, and the
508    /// chosen lifetime must not outlive the raw C string's provenance.
509    pub unsafe fn from_ptr<'a>(ptr: *const u8) -> &'a CStr8 {
510        CStr8::from_utf8_with_nul_unchecked(CStr::from_ptr(ptr.cast()).to_bytes_with_nul())
511    }
512}
513
514/// An error converting to [`CStr8`].
515///
516/// If multiple errors apply, which one you get back is unspecified.
517#[derive(Debug)]
518pub enum CStr8Error {
519    /// The string is not valid UTF-8.
520    InvalidUtf8(Utf8Error),
521    /// The string does not contain a singular terminating nul byte.
522    NulError(FromBytesWithNulError),
523}
524
525#[cfg(feature = "std")]
526impl std::error::Error for CStr8Error {
527    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
528        match self {
529            CStr8Error::InvalidUtf8(source) => Some(source),
530            CStr8Error::NulError(source) => Some(source),
531        }
532    }
533}
534
535impl fmt::Display for CStr8Error {
536    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
537        match self {
538            CStr8Error::InvalidUtf8(_) => f.write_str("invalid UTF-8"),
539            CStr8Error::NulError(_) => f.write_str("invalid nul terminator"),
540        }
541    }
542}
543
544impl From<Utf8Error> for CStr8Error {
545    fn from(source: Utf8Error) -> Self {
546        CStr8Error::InvalidUtf8(source)
547    }
548}
549
550impl From<FromBytesWithNulError> for CStr8Error {
551    fn from(source: FromBytesWithNulError) -> Self {
552        CStr8Error::NulError(source)
553    }
554}