Skip to main content

heapless/
c_string.rs

1//! A fixed capacity [`CString`](https://doc.rust-lang.org/std/ffi/struct.CString.html).
2
3use crate::{vec::Vec, CapacityError, LenType, String};
4use core::{
5    borrow::Borrow,
6    cmp::Ordering,
7    error::Error,
8    ffi::{c_char, CStr, FromBytesWithNulError},
9    fmt::{self, Display},
10    ops::Deref,
11    str::Utf8Error,
12};
13
14#[cfg(feature = "zeroize")]
15use zeroize::Zeroize;
16
17/// A fixed capacity [`CString`](https://doc.rust-lang.org/std/ffi/struct.CString.html).
18///
19/// It stores up to `N - 1` non-nul characters with a trailing nul terminator.
20#[derive(Clone, Hash)]
21pub struct CString<const N: usize, LenT: LenType = usize> {
22    inner: Vec<u8, N, LenT>,
23}
24
25#[cfg(feature = "zeroize")]
26impl<const N: usize, LenT: LenType> Zeroize for CString<N, LenT> {
27    fn zeroize(&mut self) {
28        self.inner.zeroize();
29
30        const {
31            assert!(N > 0);
32        }
33
34        // SAFETY: We just asserted that `N > 0`.
35        unsafe { self.inner.push_unchecked(b'\0') };
36    }
37}
38
39impl<const N: usize, LenT: LenType> CString<N, LenT> {
40    /// Creates a new C-compatible string with a terminating nul byte.
41    ///
42    /// ```rust
43    /// use heapless::CString;
44    ///
45    /// // A fixed-size `CString` that can store up to 10 characters
46    /// // including the nul terminator.
47    /// let empty = CString::<10>::new();
48    ///
49    /// assert_eq!(empty.as_c_str(), c"");
50    /// assert_eq!(empty.to_str(), Ok(""));
51    /// ```
52    pub fn new() -> Self {
53        const {
54            assert!(N > 0);
55        }
56
57        let mut inner = Vec::new();
58
59        // SAFETY: We just asserted that `N > 0`.
60        unsafe { inner.push_unchecked(b'\0') };
61
62        Self { inner }
63    }
64
65    /// Unsafely creates a [`CString`] from a byte slice.
66    ///
67    /// This function will copy the provided `bytes` to a [`CString`] without
68    /// performing any sanity checks.
69    ///
70    /// The function will fail if `bytes.len() > N`.
71    ///
72    /// # Safety
73    ///
74    /// The provided slice **must** be nul-terminated and not contain any interior
75    /// nul bytes.
76    ///
77    /// # Examples
78    ///
79    /// ```rust
80    /// use heapless::CString;
81    /// let mut c_string = unsafe { CString::<7>::from_bytes_with_nul_unchecked(b"string\0").unwrap() };
82    ///
83    /// assert_eq!(c_string.to_str(), Ok("string"));
84    /// ```
85    pub unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> Result<Self, CapacityError> {
86        let mut inner = Vec::new();
87
88        inner.extend_from_slice(bytes)?;
89
90        Ok(Self { inner })
91    }
92
93    /// Instantiates a [`CString`] copying from the giving byte slice, assuming it is
94    /// nul-terminated.
95    ///
96    /// Fails if the given byte slice has any interior nul byte, if the slice does not
97    /// end with a nul byte, or if the byte slice can't fit in `N`.
98    pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<Self, ExtendError> {
99        let mut string = Self::new();
100
101        string.extend_from_bytes(bytes)?;
102
103        Ok(string)
104    }
105
106    /// Instantiates a [`CString`] copying from the given byte slice, until the first nul character.
107    /// `bytes` may contain any number of nul characters, or none at all.
108    ///
109    /// This method mimics [`CStr::from_bytes_until_nul`] with two important differences:
110    /// [`Self::from_bytes_truncating_at_nul`] copies the data, and it does not fail on
111    /// non-nul terminated data.
112    ///
113    /// Fails if the given byte slice can't fit in `N`.
114    ///
115    /// # Examples
116    /// You can pass a byte array with one, many, or no nul bytes as `bytes`.
117    ///
118    /// ```rust
119    /// use heapless::CString;
120    ///
121    /// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey there!\0").unwrap();
122    /// assert_eq!(c_string.as_c_str(), c"hey there!");
123    ///
124    /// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey\0there\0!").unwrap();
125    /// assert_eq!(c_string.as_c_str(), c"hey");
126    ///
127    /// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey there!").unwrap();
128    /// assert_eq!(c_string.as_c_str(), c"hey there!");
129    /// ```
130    ///
131    /// If `bytes` is too long, an error is returned.
132    /// ```rust
133    /// use heapless::CString;
134    ///
135    /// assert!(CString::<3>::from_bytes_truncating_at_nul(b"hey\0").is_err());
136    /// ```
137    ///
138    /// `bytes` may also represent an empty string.
139    /// ```rust
140    /// use heapless::CString;
141    ///
142    /// assert_eq!(CString::<1>::from_bytes_truncating_at_nul(b"").unwrap().as_c_str(), c"");
143    /// assert_eq!(CString::<1>::from_bytes_truncating_at_nul(b"\0").unwrap().as_c_str(), c"");
144    /// ```
145    pub fn from_bytes_truncating_at_nul(bytes: &[u8]) -> Result<Self, ExtendError> {
146        // Truncate `bytes` to before the first nul byte.
147        // `bytes` will not contain any nul bytes.
148        let bytes = match CStr::from_bytes_with_nul(bytes) {
149            Ok(_) => &bytes[..bytes.len() - 1], // bytes.len() > 0, as `bytes` is nul-terminated
150            Err(FromBytesWithNulError::InteriorNul { position }) => &bytes[..position],
151            Err(FromBytesWithNulError::NotNulTerminated) => bytes,
152        };
153
154        let mut string = Self::new();
155        if let Some(capacity) = string.capacity_with_bytes(bytes) {
156            // Cannot store `bytes` due to insufficient capacity.
157            if capacity > N {
158                return Err(CapacityError.into());
159            }
160        }
161
162        // SAFETY:
163        // `string` is left in a valid state because
164        // the appended bytes do not contain any nul bytes,
165        // and we push a nul byte at the end.
166        //
167        // We've ensured above that there is enough space to push `bytes`
168        // and the nul byte.
169        unsafe { string.extend_from_bytes_unchecked(bytes) }?;
170        unsafe { string.inner.push_unchecked(0) };
171
172        Ok(string)
173    }
174
175    /// Builds a [`CString`] copying from a raw C string pointer.
176    ///
177    /// # Safety
178    ///
179    /// - The memory pointed to by `ptr` must contain a valid nul terminator at the end of the
180    ///   string.
181    /// - `ptr` must be valid for reads of bytes up to and including the nul terminator. This means
182    ///   in particular:
183    ///     - The entire memory range of this `CStr` must be contained within a single allocated
184    ///       object!
185    ///     - `ptr` must be non-nul even for a zero-length `CStr`.
186    ///
187    /// # Example
188    ///
189    /// ```rust
190    /// use core::ffi::{c_char, CStr};
191    /// use heapless::CString;
192    ///
193    /// const HELLO_PTR: *const c_char = {
194    ///     const BYTES: &[u8] = b"Hello, world!\0";
195    ///     BYTES.as_ptr().cast()
196    /// };
197    ///
198    /// let copied = unsafe { CString::<14>::from_raw(HELLO_PTR) }.unwrap();
199    ///
200    /// assert_eq!(copied.to_str(), Ok("Hello, world!"));
201    /// ```
202    pub unsafe fn from_raw(ptr: *const c_char) -> Result<Self, ExtendError> {
203        // SAFETY: The given pointer to a string is assumed to be nul-terminated.
204        Self::from_bytes_with_nul(unsafe { CStr::from_ptr(ptr).to_bytes_with_nul() })
205    }
206
207    /// Converts the [`CString`] to a [`CStr`] slice.
208    #[inline]
209    pub fn as_c_str(&self) -> &CStr {
210        unsafe { CStr::from_bytes_with_nul_unchecked(&self.inner) }
211    }
212
213    /// Calculates the length of `self.inner` would have if it appended `bytes`.
214    fn capacity_with_bytes(&self, bytes: &[u8]) -> Option<usize> {
215        match bytes.last() {
216            None => None,
217            Some(0) if bytes.len() < 2 => None,
218            Some(0) => {
219                // `bytes` is nul-terminated and so is `self.inner`.
220                // Adding up both would account for 2 nul bytes when only a single byte
221                // would end up in the resulting CString.
222                Some(self.inner.len() + bytes.len() - 1)
223            }
224            Some(_) => {
225                // No terminating nul byte in `bytes` but there's one in
226                // `self.inner`, so the math lines up nicely.
227                //
228                // In the case that `bytes` has a nul byte anywhere else, we would
229                // error after `memchr` is called. So there's no problem.
230                Some(self.inner.len() + bytes.len())
231            }
232        }
233    }
234
235    /// Extends the [`CString`] with the given bytes.
236    ///
237    /// This function fails if the [`CString`] would not have enough capacity to append the bytes or
238    /// if the bytes contain an interior nul byte.
239    ///
240    /// # Example
241    ///
242    /// ```rust
243    /// use heapless::CString;
244    ///
245    /// let mut c_string = CString::<10>::new();
246    ///
247    /// c_string.extend_from_bytes(b"hey").unwrap();
248    /// c_string.extend_from_bytes(b" there\0").unwrap();
249    ///
250    /// assert_eq!(c_string.to_str(), Ok("hey there"));
251    /// ```
252    pub fn extend_from_bytes(&mut self, bytes: &[u8]) -> Result<(), ExtendError> {
253        let Some(capacity) = self.capacity_with_bytes(bytes) else {
254            return Ok(());
255        };
256
257        if capacity > N {
258            // Cannot store these bytes due to an insufficient capacity.
259            return Err(CapacityError.into());
260        }
261
262        match CStr::from_bytes_with_nul(bytes) {
263            Ok(_) => {
264                // SAFETY: A string is left in a valid state because appended bytes are
265                // nul-terminated.
266                unsafe { self.extend_from_bytes_unchecked(bytes) }?;
267
268                Ok(())
269            }
270            Err(FromBytesWithNulError::InteriorNul { position }) => {
271                Err(ExtendError::InteriorNul { position })
272            }
273            Err(FromBytesWithNulError::NotNulTerminated) => {
274                // Because given bytes has no nul byte anywhere, we insert the bytes and
275                // then add the nul byte terminator.
276                //
277                // We've ensured above that we have enough space left to insert these bytes,
278                // so the operations below must succeed.
279                //
280                // SAFETY: We append a missing nul terminator right below.
281                unsafe {
282                    self.extend_from_bytes_unchecked(bytes).unwrap();
283                    self.inner.push_unchecked(0);
284                };
285
286                Ok(())
287            }
288        }
289    }
290
291    /// Removes the nul byte terminator from the inner buffer.
292    ///
293    /// # Safety
294    ///
295    /// Callers must ensure to add the nul terminator back after this function is called.
296    #[inline]
297    unsafe fn pop_terminator(&mut self) {
298        debug_assert_eq!(self.inner.last(), Some(&0));
299
300        // SAFETY: We always have the nul terminator at the end.
301        unsafe { self.inner.pop_unchecked() };
302    }
303
304    /// Removes the existing nul terminator and then extends `self` with the given bytes.
305    ///
306    /// # Safety
307    ///
308    /// If `additional` is not nul-terminated, the [`CString`] is left non nul-terminated, which is
309    /// an invalid state. Caller must ensure that either `additional` has a terminating nul byte
310    /// or ensure to append a trailing nul terminator.
311    unsafe fn extend_from_bytes_unchecked(
312        &mut self,
313        additional: &[u8],
314    ) -> Result<(), CapacityError> {
315        // SAFETY: A caller is responsible for adding a nul terminator back to the inner buffer.
316        unsafe { self.pop_terminator() }
317
318        self.inner.extend_from_slice(additional)
319    }
320
321    /// Returns the underlying byte slice including the trailing nul terminator.
322    ///
323    /// # Example
324    ///
325    /// ```rust
326    /// use heapless::CString;
327    ///
328    /// let mut c_string = CString::<5>::new();
329    /// c_string.extend_from_bytes(b"abc").unwrap();
330    ///
331    /// assert_eq!(c_string.as_bytes_with_nul(), b"abc\0");
332    /// ```
333    #[inline]
334    pub fn as_bytes_with_nul(&self) -> &[u8] {
335        &self.inner
336    }
337
338    /// Returns the underlying byte slice excluding the trailing nul terminator.
339    ///
340    /// # Example
341    ///
342    /// ```rust
343    /// use heapless::CString;
344    ///
345    /// let mut c_string = CString::<5>::new();
346    /// c_string.extend_from_bytes(b"abc").unwrap();
347    ///
348    /// assert_eq!(c_string.as_bytes(), b"abc");
349    /// ```
350    #[inline]
351    pub fn as_bytes(&self) -> &[u8] {
352        &self.inner[..self.inner.len() - 1]
353    }
354
355    /// Converts the [`CString`] into a [`String`] if it contains valid UTF-8 data.
356    ///
357    /// On failure, ownership of the original [`CString`] is returned.
358    ///
359    /// Equivalent of `std::ffi::CString::into_string`.
360    ///
361    /// # Examples
362    /// Valid UTF-8:
363    ///
364    /// ```rust
365    /// use heapless::CString;
366    ///
367    /// let sparkle_heart = CString::<5>::from_bytes_with_nul(&[240, 159, 146, 150, 0]).unwrap();
368    /// assert_eq!(sparkle_heart.into_string().unwrap(), "💖");
369    /// ```
370    ///
371    /// Invalid UTF-8:
372    ///
373    /// ```rust
374    /// use heapless::CString;
375    ///
376    /// let hello_world = CString::<16>::from_bytes_with_nul(b"Hello \xF0\x90\x80World\0").unwrap();
377    /// assert!(hello_world.into_string().is_err());
378    /// ```
379    pub fn into_string(self) -> Result<String<N, LenT>, IntoStringError<N, LenT>> {
380        // `String::from_utf8(self.inner)` would be a great fit here,
381        // but the error type of that method does not return ownership.
382        if let Err(error) = core::str::from_utf8(self.as_bytes()) {
383            return Err(IntoStringError { inner: self, error });
384        }
385
386        // SAFETY: UTF-8 invariant has just been checked by `str::from_utf8`.
387        Ok(unsafe { String::from_utf8_unchecked(self.into_bytes()) })
388    }
389
390    #[inline]
391    /// Consumes the [`CString`] and returns the underlying byte buffer.
392    ///
393    /// The returned byte buffer *does* contain the trailing nul terminator.
394    /// It is guaranteed that the returned buffer does not contain any interior nul bytes.
395    ///
396    /// Equivalent of `std::ffi::CString::into_bytes_with_nul`.
397    ///
398    /// # Examples
399    /// ```rust
400    /// use heapless::CString;
401    /// let c_string = CString::<16>::from_bytes_with_nul(b"Hello World!\0").unwrap();
402    ///
403    /// assert_eq!(c_string.into_bytes_with_nul(), b"Hello World!\0");
404    /// ```
405    pub fn into_bytes_with_nul(self) -> Vec<u8, N, LenT> {
406        self.inner
407    }
408
409    /// Consumes the [`CString`] and returns the underlying byte buffer.
410    ///
411    /// The returned byte buffer does *not* contain the trailing nul terminator,
412    /// and it guaranteed to not contain any interior nul bytes.
413    ///
414    /// Equivalent of `std::ffi::CString::into_bytes`.
415    ///
416    /// # Examples
417    /// ```rust
418    /// use heapless::CString;
419    /// let c_string = CString::<16>::from_bytes_with_nul(b"Hello World!\0").unwrap();
420    ///
421    /// assert_eq!(c_string.into_bytes(), b"Hello World!");
422    /// ```
423    pub fn into_bytes(self) -> Vec<u8, N, LenT> {
424        let mut vec = self.into_bytes_with_nul();
425        let _nul = vec.pop();
426        debug_assert_eq!(_nul, Some(0u8));
427        vec
428    }
429}
430
431impl<const N: usize, LenT: LenType> AsRef<CStr> for CString<N, LenT> {
432    #[inline]
433    fn as_ref(&self) -> &CStr {
434        self.as_c_str()
435    }
436}
437
438impl<const N: usize, LenT: LenType> Borrow<CStr> for CString<N, LenT> {
439    #[inline]
440    fn borrow(&self) -> &CStr {
441        self.as_c_str()
442    }
443}
444
445impl<const N: usize, LenT: LenType> Default for CString<N, LenT> {
446    #[inline]
447    fn default() -> Self {
448        Self::new()
449    }
450}
451
452impl<const N: usize, LenT: LenType> Deref for CString<N, LenT> {
453    type Target = CStr;
454
455    #[inline]
456    fn deref(&self) -> &Self::Target {
457        self.as_c_str()
458    }
459}
460
461impl<const N: usize, const M: usize, LenT1: LenType, LenT2: LenType> PartialEq<CString<M, LenT2>>
462    for CString<N, LenT1>
463{
464    #[inline]
465    fn eq(&self, rhs: &CString<M, LenT2>) -> bool {
466        self.as_c_str() == rhs.as_c_str()
467    }
468}
469
470impl<const N: usize, LenT: LenType> Eq for CString<N, LenT> {}
471
472impl<const N: usize, const M: usize, LenT1: LenType, LenT2: LenType> PartialOrd<CString<M, LenT2>>
473    for CString<N, LenT1>
474{
475    #[inline]
476    fn partial_cmp(&self, rhs: &CString<M, LenT2>) -> Option<Ordering> {
477        self.as_c_str().partial_cmp(rhs.as_c_str())
478    }
479}
480
481impl<const N: usize, LenT: LenType> Ord for CString<N, LenT> {
482    #[inline]
483    fn cmp(&self, rhs: &Self) -> Ordering {
484        self.as_c_str().cmp(rhs.as_c_str())
485    }
486}
487
488impl<const N: usize, LenT: LenType> fmt::Debug for CString<N, LenT> {
489    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490        self.as_c_str().fmt(f)
491    }
492}
493
494#[derive(Debug, Clone, PartialEq)]
495/// An error indicating invalid UTF-8 when converting a [`CString`] into a [`String`].
496///
497/// This struct is created by [`CString::into_string()`].
498///
499/// Equivalent of `std::ffi::IntoStringError`.
500///
501/// Call [`Self::into_cstring`] to regain ownership of the [`CString`].
502///
503/// # Examples
504/// ```rust
505/// use heapless::CString;
506/// use heapless::c_string::IntoStringError;
507///
508/// // the byte slice contains invalid UTF-8
509/// let hello_world = CString::<16>::from_bytes_with_nul(b"Hello \xF0\x90\x80World\0").unwrap();
510/// let hello_world_clone = hello_world.clone();
511///
512/// let err: IntoStringError<16> = hello_world.into_string().unwrap_err();
513///
514/// assert_eq!(err.utf8_error().valid_up_to(), 6);
515/// assert_eq!(err.utf8_error().error_len(), Some(3));
516/// assert_eq!(err.into_cstring(), hello_world_clone);
517/// ```
518pub struct IntoStringError<const N: usize, LenT = usize>
519where
520    LenT: LenType,
521{
522    inner: CString<N, LenT>,
523    error: Utf8Error,
524}
525
526impl<const N: usize, LenT: LenType> IntoStringError<N, LenT> {
527    #[inline]
528    /// Consumes this error, returning original [`CString`] which generated the error.
529    pub fn into_cstring(self) -> CString<N, LenT> {
530        self.inner
531    }
532
533    #[inline]
534    /// Access the underlying UTF-8 error that was the cause of this error.
535    pub fn utf8_error(&self) -> Utf8Error {
536        self.error
537    }
538}
539
540impl<const N: usize, LenT: LenType> Display for IntoStringError<N, LenT> {
541    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
542        "CString contained non-utf8 bytes".fmt(f)
543    }
544}
545
546impl<const N: usize, LenT: LenType> Error for IntoStringError<N, LenT> {
547    fn source(&self) -> Option<&(dyn Error + 'static)> {
548        Some(&self.error)
549    }
550}
551
552/// An error to extend [`CString`] with bytes.
553#[derive(Debug)]
554pub enum ExtendError {
555    /// The capacity of the [`CString`] is too small.
556    Capacity(CapacityError),
557    /// An invalid interior nul byte found in a given byte slice.
558    InteriorNul {
559        /// A position of a nul byte.
560        position: usize,
561    },
562}
563
564impl Error for ExtendError {}
565
566impl fmt::Display for ExtendError {
567    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
568        match self {
569            Self::Capacity(error) => write!(f, "{error}"),
570            Self::InteriorNul { position } => write!(f, "interior nul byte at {position}"),
571        }
572    }
573}
574
575impl From<CapacityError> for ExtendError {
576    fn from(error: CapacityError) -> Self {
577        Self::Capacity(error)
578    }
579}
580
581#[cfg(test)]
582mod tests {
583    use super::*;
584
585    #[test]
586    fn empty() {
587        let empty = CString::<1>::new();
588
589        assert_eq!(empty.as_c_str(), c"");
590        assert_eq!(empty.as_bytes(), &[]);
591        assert_eq!(empty.to_str(), Ok(""));
592    }
593
594    #[test]
595    fn create_with_capacity_error() {
596        assert!(CString::<1>::from_bytes_with_nul(b"a\0").is_err());
597    }
598
599    #[test]
600    fn extend_no_byte() {
601        let mut c_string = CString::<1>::new();
602
603        c_string.extend_from_bytes(b"").unwrap();
604    }
605
606    #[test]
607    fn extend_from_bytes() {
608        let mut c_string = CString::<11>::new();
609        assert_eq!(c_string.to_str(), Ok(""));
610
611        c_string.extend_from_bytes(b"hello").unwrap();
612
613        assert_eq!(c_string.to_str(), Ok("hello"));
614
615        // Call must fail since `w\0rld` contains an interior nul byte.
616        assert!(matches!(
617            c_string.extend_from_bytes(b"w\0rld"),
618            Err(ExtendError::InteriorNul { position: 1 })
619        ));
620
621        // However, the call above _must not_ have invalidated the state of our CString
622        assert_eq!(c_string.to_str(), Ok("hello"));
623
624        // Call must fail since we can't store "hello world\0" in 11 bytes
625        assert!(matches!(
626            c_string.extend_from_bytes(b" world"),
627            Err(ExtendError::Capacity(CapacityError))
628        ));
629
630        // Yet again, the call above must not have invalidated the state of our CString
631        // (as it would e.g. if we pushed the bytes but then failed to push the nul terminator)
632        assert_eq!(c_string.to_str(), Ok("hello"));
633
634        c_string.extend_from_bytes(b" Bill").unwrap();
635
636        assert_eq!(c_string.to_str(), Ok("hello Bill"));
637    }
638
639    #[test]
640    fn calculate_capacity_with_additional_bytes() {
641        const INITIAL_BYTES: &[u8] = b"abc";
642
643        let mut c_string = CString::<5>::new();
644
645        c_string.extend_from_bytes(INITIAL_BYTES).unwrap();
646
647        assert_eq!(c_string.to_bytes_with_nul().len(), 4);
648        assert_eq!(c_string.capacity_with_bytes(b""), None);
649        assert_eq!(c_string.capacity_with_bytes(b"\0"), None);
650        assert_eq!(
651            c_string.capacity_with_bytes(b"d"),
652            Some(INITIAL_BYTES.len() + 2)
653        );
654        assert_eq!(
655            c_string.capacity_with_bytes(b"d\0"),
656            Some(INITIAL_BYTES.len() + 2)
657        );
658        assert_eq!(
659            c_string.capacity_with_bytes(b"defg"),
660            Some(INITIAL_BYTES.len() + 5)
661        );
662        assert_eq!(
663            c_string.capacity_with_bytes(b"defg\0"),
664            Some(INITIAL_BYTES.len() + 5)
665        );
666    }
667
668    #[test]
669    fn into_bytes_empty() {
670        let c_string = CString::<16>::from_bytes_with_nul(b"\0").unwrap();
671        assert_eq!(c_string.into_bytes(), b"");
672    }
673
674    #[test]
675    fn into_bytes_with_nul_empty() {
676        let c_string = CString::<16>::from_bytes_with_nul(b"\0").unwrap();
677        assert_eq!(c_string.into_bytes_with_nul(), b"\0");
678    }
679
680    #[test]
681    fn into_string_empty() {
682        let c_string = CString::<16>::from_bytes_with_nul(b"\0").unwrap();
683        assert_eq!(c_string.into_string().unwrap(), "");
684    }
685
686    #[test]
687    fn default() {
688        assert_eq!(CString::<1>::default().as_c_str(), c"");
689    }
690
691    #[test]
692    fn deref() {
693        assert_eq!(CString::<1>::new().deref(), c"");
694        assert_eq!(CString::<2>::new().deref(), c"");
695        assert_eq!(CString::<3>::new().deref(), c"");
696
697        let mut string = CString::<2>::new();
698        string.extend_from_bytes(&[65]).unwrap();
699
700        assert_eq!(string.deref(), c"A");
701
702        let mut string = CString::<3>::new();
703        string.extend_from_bytes(&[65, 66]).unwrap();
704
705        assert_eq!(string.deref(), c"AB");
706
707        let mut string = CString::<4>::new();
708        string.extend_from_bytes(&[65, 66, 67]).unwrap();
709
710        assert_eq!(string.deref(), c"ABC");
711    }
712
713    #[test]
714    fn as_ref() {
715        let mut string = CString::<4>::new();
716        string.extend_from_bytes(b"foo").unwrap();
717        assert_eq!(string.as_ref(), c"foo");
718    }
719
720    #[test]
721    fn borrow() {
722        let mut string = CString::<4>::new();
723        string.extend_from_bytes(b"foo").unwrap();
724        assert_eq!(Borrow::<CStr>::borrow(&string), c"foo");
725    }
726
727    #[test]
728    #[cfg(feature = "zeroize")]
729    fn test_cstring_zeroize() {
730        use zeroize::Zeroize;
731
732        let mut c_string = CString::<32>::from_bytes_with_nul(b"sensitive_password\0").unwrap();
733
734        assert_eq!(c_string.to_str(), Ok("sensitive_password"));
735        assert!(!c_string.to_bytes().is_empty());
736        let original_length = c_string.to_bytes().len();
737        assert_eq!(original_length, 18);
738
739        let new_string = CString::<32>::from_bytes_with_nul(b"short\0").unwrap();
740        c_string = new_string;
741
742        assert_eq!(c_string.to_str(), Ok("short"));
743        assert_eq!(c_string.to_bytes().len(), 5);
744
745        // zeroized using Vec's implementation
746        c_string.zeroize();
747
748        assert_eq!(c_string.to_bytes().len(), 0);
749        assert_eq!(c_string.to_bytes_with_nul(), &[0]);
750
751        c_string.extend_from_bytes(b"new_data").unwrap();
752        assert_eq!(c_string.to_str(), Ok("new_data"));
753        assert_eq!(c_string.to_bytes().len(), 8);
754    }
755
756    mod equality {
757        use super::*;
758
759        #[test]
760        fn c_string() {
761            // Empty strings
762            assert!(CString::<1>::new() == CString::<1>::new());
763            assert!(CString::<1>::new() == CString::<2>::new());
764            assert!(CString::<1>::from_bytes_with_nul(b"\0").unwrap() == CString::<3>::new());
765
766            // Single character
767            assert!(
768                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
769                    == CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
770            );
771            assert!(
772                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
773                    == CString::<3>::from_bytes_with_nul(b"a\0").unwrap()
774            );
775            assert!(
776                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
777                    != CString::<2>::from_bytes_with_nul(b"b\0").unwrap()
778            );
779
780            // Multiple characters
781            assert!(
782                CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
783                    == CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
784            );
785            assert!(
786                CString::<3>::from_bytes_with_nul(b"ab\0").unwrap()
787                    != CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
788            );
789        }
790    }
791
792    mod ordering {
793        use super::*;
794
795        #[test]
796        fn c_string() {
797            assert_eq!(
798                CString::<1>::new().partial_cmp(&CString::<1>::new()),
799                Some(Ordering::Equal)
800            );
801            assert_eq!(
802                CString::<2>::from_bytes_with_nul(b"a\0")
803                    .unwrap()
804                    .partial_cmp(&CString::<2>::from_bytes_with_nul(b"b\0").unwrap()),
805                Some(Ordering::Less)
806            );
807            assert_eq!(
808                CString::<2>::from_bytes_with_nul(b"b\0")
809                    .unwrap()
810                    .partial_cmp(&CString::<2>::from_bytes_with_nul(b"a\0").unwrap()),
811                Some(Ordering::Greater)
812            );
813        }
814
815        #[test]
816        fn c_str() {
817            assert_eq!(c"".partial_cmp(&CString::<1>::new()), Some(Ordering::Equal));
818            assert_eq!(
819                c"a".partial_cmp(&CString::<2>::from_bytes_with_nul(b"b\0").unwrap()),
820                Some(Ordering::Less)
821            );
822            assert_eq!(
823                c"b".partial_cmp(&CString::<2>::from_bytes_with_nul(b"a\0").unwrap()),
824                Some(Ordering::Greater)
825            );
826        }
827    }
828}