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};
4use core::{
5    borrow::Borrow,
6    cmp::Ordering,
7    error::Error,
8    ffi::{c_char, CStr, FromBytesWithNulError},
9    fmt,
10    ops::Deref,
11};
12
13/// A fixed capacity [`CString`](https://doc.rust-lang.org/std/ffi/struct.CString.html).
14///
15/// It stores up to `N - 1` non-nul characters with a trailing nul terminator.
16#[derive(Clone, Hash)]
17pub struct CString<const N: usize, LenT: LenType = usize> {
18    inner: Vec<u8, N, LenT>,
19}
20
21impl<const N: usize, LenT: LenType> CString<N, LenT> {
22    /// Creates a new C-compatible string with a terminating nul byte.
23    ///
24    /// ```rust
25    /// use heapless::CString;
26    ///
27    /// // A fixed-size `CString` that can store up to 10 characters
28    /// // including the nul terminator.
29    /// let empty = CString::<10>::new();
30    ///
31    /// assert_eq!(empty.as_c_str(), c"");
32    /// assert_eq!(empty.to_str(), Ok(""));
33    /// ```
34    pub fn new() -> Self {
35        const {
36            assert!(N > 0);
37        }
38
39        let mut inner = Vec::new();
40
41        // SAFETY: We just asserted that `N > 0`.
42        unsafe { inner.push_unchecked(b'\0') };
43
44        Self { inner }
45    }
46
47    /// Unsafely creates a [`CString`] from a byte slice.
48    ///
49    /// This function will copy the provided `bytes` to a [`CString`] without
50    /// performing any sanity checks.
51    ///
52    /// The function will fail if `bytes.len() > N`.
53    ///
54    /// # Safety
55    ///
56    /// The provided slice **must** be nul-terminated and not contain any interior
57    /// nul bytes.
58    ///
59    /// # Examples
60    ///
61    /// ```rust
62    /// use heapless::CString;
63    /// let mut c_string = unsafe { CString::<7>::from_bytes_with_nul_unchecked(b"string\0").unwrap() };
64    ///
65    /// assert_eq!(c_string.to_str(), Ok("string"));
66    /// ```
67    pub unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> Result<Self, CapacityError> {
68        let mut inner = Vec::new();
69
70        inner.extend_from_slice(bytes)?;
71
72        Ok(Self { inner })
73    }
74
75    /// Instantiates a [`CString`] copying from the giving byte slice, assuming it is
76    /// nul-terminated.
77    ///
78    /// Fails if the given byte slice has any interior nul byte, if the slice does not
79    /// end with a nul byte, or if the byte slice can't fit in `N`.
80    pub fn from_bytes_with_nul(bytes: &[u8]) -> Result<Self, ExtendError> {
81        let mut string = Self::new();
82
83        string.extend_from_bytes(bytes)?;
84
85        Ok(string)
86    }
87
88    /// Builds a [`CString`] copying from a raw C string pointer.
89    ///
90    /// # Safety
91    ///
92    /// - The memory pointed to by `ptr` must contain a valid nul terminator at the
93    ///   end of the string.
94    /// - `ptr` must be valid for reads of bytes up to and including the nul terminator.
95    ///   This means in particular:
96    ///     - The entire memory range of this `CStr` must be contained within a single allocated object!
97    ///     - `ptr` must be non-nul even for a zero-length `CStr`.
98    ///
99    /// # Example
100    ///
101    /// ```rust
102    /// use core::ffi::{c_char, CStr};
103    /// use heapless::CString;
104    ///
105    /// const HELLO_PTR: *const c_char = {
106    ///     const BYTES: &[u8] = b"Hello, world!\0";
107    ///     BYTES.as_ptr().cast()
108    /// };
109    ///
110    /// let copied = unsafe { CString::<14>::from_raw(HELLO_PTR) }.unwrap();
111    ///
112    /// assert_eq!(copied.to_str(), Ok("Hello, world!"));
113    /// ```
114    pub unsafe fn from_raw(ptr: *const c_char) -> Result<Self, ExtendError> {
115        // SAFETY: The given pointer to a string is assumed to be nul-terminated.
116        Self::from_bytes_with_nul(unsafe { CStr::from_ptr(ptr).to_bytes_with_nul() })
117    }
118
119    /// Converts the [`CString`] to a [`CStr`] slice.
120    #[inline]
121    pub fn as_c_str(&self) -> &CStr {
122        unsafe { CStr::from_bytes_with_nul_unchecked(&self.inner) }
123    }
124
125    /// Calculates the length of `self.inner` would have if it appended `bytes`.
126    fn capacity_with_bytes(&self, bytes: &[u8]) -> Option<usize> {
127        match bytes.last() {
128            None => None,
129            Some(0) if bytes.len() < 2 => None,
130            Some(0) => {
131                // `bytes` is nul-terminated and so is `self.inner`.
132                // Adding up both would account for 2 nul bytes when only a single byte
133                // would end up in the resulting CString.
134                Some(self.inner.len() + bytes.len() - 1)
135            }
136            Some(_) => {
137                // No terminating nul byte in `bytes` but there's one in
138                // `self.inner`, so the math lines up nicely.
139                //
140                // In the case that `bytes` has a nul byte anywhere else, we would
141                // error after `memchr` is called. So there's no problem.
142                Some(self.inner.len() + bytes.len())
143            }
144        }
145    }
146
147    /// Extends the [`CString`] with the given bytes.
148    ///
149    /// This function fails if the [`CString`] would not have enough capacity to append the bytes or
150    /// if the bytes contain an interior nul byte.
151    ///
152    /// # Example
153    ///
154    /// ```rust
155    /// use heapless::CString;
156    ///
157    /// let mut c_string = CString::<10>::new();
158    ///
159    /// c_string.extend_from_bytes(b"hey").unwrap();
160    /// c_string.extend_from_bytes(b" there\0").unwrap();
161    ///
162    /// assert_eq!(c_string.to_str(), Ok("hey there"));
163    /// ```
164    pub fn extend_from_bytes(&mut self, bytes: &[u8]) -> Result<(), ExtendError> {
165        let Some(capacity) = self.capacity_with_bytes(bytes) else {
166            return Ok(());
167        };
168
169        if capacity > N {
170            // Cannot store these bytes due to an insufficient capacity.
171            return Err(CapacityError.into());
172        }
173
174        match CStr::from_bytes_with_nul(bytes) {
175            Ok(_) => {
176                // SAFETY: A string is left in a valid state because appended bytes are nul-terminated.
177                unsafe { self.extend_from_bytes_unchecked(bytes) }?;
178
179                Ok(())
180            }
181            Err(FromBytesWithNulError::InteriorNul { position }) => {
182                Err(ExtendError::InteriorNul { position })
183            }
184            Err(FromBytesWithNulError::NotNulTerminated) => {
185                // Because given bytes has no nul byte anywhere, we insert the bytes and
186                // then add the nul byte terminator.
187                //
188                // We've ensured above that we have enough space left to insert these bytes,
189                // so the operations below must succeed.
190                //
191                // SAFETY: We append a missing nul terminator right below.
192                unsafe {
193                    self.extend_from_bytes_unchecked(bytes).unwrap();
194                    self.inner.push_unchecked(0);
195                };
196
197                Ok(())
198            }
199        }
200    }
201
202    /// Removes the nul byte terminator from the inner buffer.
203    ///
204    /// # Safety
205    ///
206    /// Callers must ensure to add the nul terminator back after this function is called.
207    #[inline]
208    unsafe fn pop_terminator(&mut self) {
209        debug_assert_eq!(self.inner.last(), Some(&0));
210
211        // SAFETY: We always have the nul terminator at the end.
212        unsafe { self.inner.pop_unchecked() };
213    }
214
215    /// Removes the existing nul terminator and then extends `self` with the given bytes.
216    ///
217    /// # Safety
218    ///
219    /// If `additional` is not nul-terminated, the [`CString`] is left non nul-terminated, which is
220    /// an invalid state. Caller must ensure that either `additional` has a terminating nul byte
221    /// or ensure to append a trailing nul terminator.
222    unsafe fn extend_from_bytes_unchecked(
223        &mut self,
224        additional: &[u8],
225    ) -> Result<(), CapacityError> {
226        // SAFETY: A caller is responsible for adding a nul terminator back to the inner buffer.
227        unsafe { self.pop_terminator() }
228
229        self.inner.extend_from_slice(additional)
230    }
231
232    /// Returns the underlying byte slice including the trailing nul terminator.
233    ///
234    /// # Example
235    ///
236    /// ```rust
237    /// use heapless::CString;
238    ///
239    /// let mut c_string = CString::<5>::new();
240    /// c_string.extend_from_bytes(b"abc").unwrap();
241    ///
242    /// assert_eq!(c_string.as_bytes_with_nul(), b"abc\0");
243    /// ```
244    #[inline]
245    pub fn as_bytes_with_nul(&self) -> &[u8] {
246        &self.inner
247    }
248
249    /// Returns the underlying byte slice excluding the trailing nul terminator.
250    ///
251    /// # Example
252    ///
253    /// ```rust
254    /// use heapless::CString;
255    ///
256    /// let mut c_string = CString::<5>::new();
257    /// c_string.extend_from_bytes(b"abc").unwrap();
258    ///
259    /// assert_eq!(c_string.as_bytes(), b"abc");
260    /// ```
261    #[inline]
262    pub fn as_bytes(&self) -> &[u8] {
263        &self.inner[..self.inner.len() - 1]
264    }
265}
266
267impl<const N: usize, LenT: LenType> AsRef<CStr> for CString<N, LenT> {
268    #[inline]
269    fn as_ref(&self) -> &CStr {
270        self.as_c_str()
271    }
272}
273
274impl<const N: usize, LenT: LenType> Borrow<CStr> for CString<N, LenT> {
275    #[inline]
276    fn borrow(&self) -> &CStr {
277        self.as_c_str()
278    }
279}
280
281impl<const N: usize, LenT: LenType> Default for CString<N, LenT> {
282    #[inline]
283    fn default() -> Self {
284        Self::new()
285    }
286}
287
288impl<const N: usize, LenT: LenType> Deref for CString<N, LenT> {
289    type Target = CStr;
290
291    #[inline]
292    fn deref(&self) -> &Self::Target {
293        self.as_c_str()
294    }
295}
296
297impl<const N: usize, const M: usize, LenT1: LenType, LenT2: LenType> PartialEq<CString<M, LenT2>>
298    for CString<N, LenT1>
299{
300    #[inline]
301    fn eq(&self, rhs: &CString<M, LenT2>) -> bool {
302        self.as_c_str() == rhs.as_c_str()
303    }
304}
305
306impl<const N: usize, LenT: LenType> Eq for CString<N, LenT> {}
307
308impl<const N: usize, const M: usize, LenT1: LenType, LenT2: LenType> PartialOrd<CString<M, LenT2>>
309    for CString<N, LenT1>
310{
311    #[inline]
312    fn partial_cmp(&self, rhs: &CString<M, LenT2>) -> Option<Ordering> {
313        self.as_c_str().partial_cmp(rhs.as_c_str())
314    }
315}
316
317impl<const N: usize, LenT: LenType> Ord for CString<N, LenT> {
318    #[inline]
319    fn cmp(&self, rhs: &Self) -> Ordering {
320        self.as_c_str().cmp(rhs.as_c_str())
321    }
322}
323
324impl<const N: usize, LenT: LenType> fmt::Debug for CString<N, LenT> {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        self.as_c_str().fmt(f)
327    }
328}
329
330/// An error to extend [`CString`] with bytes.
331#[derive(Debug)]
332pub enum ExtendError {
333    /// The capacity of the [`CString`] is too small.
334    Capacity(CapacityError),
335    /// An invalid interior nul byte found in a given byte slice.
336    InteriorNul {
337        /// A position of a nul byte.
338        position: usize,
339    },
340}
341
342impl Error for ExtendError {}
343
344impl fmt::Display for ExtendError {
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        match self {
347            Self::Capacity(error) => write!(f, "{error}"),
348            Self::InteriorNul { position } => write!(f, "interior nul byte at {position}"),
349        }
350    }
351}
352
353impl From<CapacityError> for ExtendError {
354    fn from(error: CapacityError) -> Self {
355        Self::Capacity(error)
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::*;
362
363    #[test]
364    fn empty() {
365        let empty = CString::<1>::new();
366
367        assert_eq!(empty.as_c_str(), c"");
368        assert_eq!(empty.as_bytes(), &[]);
369        assert_eq!(empty.to_str(), Ok(""));
370    }
371
372    #[test]
373    fn create_with_capacity_error() {
374        assert!(CString::<1>::from_bytes_with_nul(b"a\0").is_err());
375    }
376
377    #[test]
378    fn extend_no_byte() {
379        let mut c_string = CString::<1>::new();
380
381        c_string.extend_from_bytes(b"").unwrap();
382    }
383
384    #[test]
385    fn extend_from_bytes() {
386        let mut c_string = CString::<11>::new();
387        assert_eq!(c_string.to_str(), Ok(""));
388
389        c_string.extend_from_bytes(b"hello").unwrap();
390
391        assert_eq!(c_string.to_str(), Ok("hello"));
392
393        // Call must fail since `w\0rld` contains an interior nul byte.
394        assert!(matches!(
395            c_string.extend_from_bytes(b"w\0rld"),
396            Err(ExtendError::InteriorNul { position: 1 })
397        ));
398
399        // However, the call above _must not_ have invalidated the state of our CString
400        assert_eq!(c_string.to_str(), Ok("hello"));
401
402        // Call must fail since we can't store "hello world\0" in 11 bytes
403        assert!(matches!(
404            c_string.extend_from_bytes(b" world"),
405            Err(ExtendError::Capacity(CapacityError))
406        ));
407
408        // Yet again, the call above must not have invalidated the state of our CString
409        // (as it would e.g. if we pushed the bytes but then failed to push the nul terminator)
410        assert_eq!(c_string.to_str(), Ok("hello"));
411
412        c_string.extend_from_bytes(b" Bill").unwrap();
413
414        assert_eq!(c_string.to_str(), Ok("hello Bill"));
415    }
416
417    #[test]
418    fn calculate_capacity_with_additional_bytes() {
419        const INITIAL_BYTES: &[u8] = b"abc";
420
421        let mut c_string = CString::<5>::new();
422
423        c_string.extend_from_bytes(INITIAL_BYTES).unwrap();
424
425        assert_eq!(c_string.to_bytes_with_nul().len(), 4);
426        assert_eq!(c_string.capacity_with_bytes(b""), None);
427        assert_eq!(c_string.capacity_with_bytes(b"\0"), None);
428        assert_eq!(
429            c_string.capacity_with_bytes(b"d"),
430            Some(INITIAL_BYTES.len() + 2)
431        );
432        assert_eq!(
433            c_string.capacity_with_bytes(b"d\0"),
434            Some(INITIAL_BYTES.len() + 2)
435        );
436        assert_eq!(
437            c_string.capacity_with_bytes(b"defg"),
438            Some(INITIAL_BYTES.len() + 5)
439        );
440        assert_eq!(
441            c_string.capacity_with_bytes(b"defg\0"),
442            Some(INITIAL_BYTES.len() + 5)
443        );
444    }
445    #[test]
446    fn default() {
447        assert_eq!(CString::<1>::default().as_c_str(), c"");
448    }
449
450    #[test]
451    fn deref() {
452        assert_eq!(CString::<1>::new().deref(), c"");
453        assert_eq!(CString::<2>::new().deref(), c"");
454        assert_eq!(CString::<3>::new().deref(), c"");
455
456        let mut string = CString::<2>::new();
457        string.extend_from_bytes(&[65]).unwrap();
458
459        assert_eq!(string.deref(), c"A");
460
461        let mut string = CString::<3>::new();
462        string.extend_from_bytes(&[65, 66]).unwrap();
463
464        assert_eq!(string.deref(), c"AB");
465
466        let mut string = CString::<4>::new();
467        string.extend_from_bytes(&[65, 66, 67]).unwrap();
468
469        assert_eq!(string.deref(), c"ABC");
470    }
471
472    #[test]
473    fn as_ref() {
474        let mut string = CString::<4>::new();
475        string.extend_from_bytes(b"foo").unwrap();
476        assert_eq!(string.as_ref(), c"foo");
477    }
478
479    #[test]
480    fn borrow() {
481        let mut string = CString::<4>::new();
482        string.extend_from_bytes(b"foo").unwrap();
483        assert_eq!(Borrow::<CStr>::borrow(&string), c"foo");
484    }
485
486    mod equality {
487        use super::*;
488
489        #[test]
490        fn c_string() {
491            // Empty strings
492            assert!(CString::<1>::new() == CString::<1>::new());
493            assert!(CString::<1>::new() == CString::<2>::new());
494            assert!(CString::<1>::from_bytes_with_nul(b"\0").unwrap() == CString::<3>::new());
495
496            // Single character
497            assert!(
498                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
499                    == CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
500            );
501            assert!(
502                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
503                    == CString::<3>::from_bytes_with_nul(b"a\0").unwrap()
504            );
505            assert!(
506                CString::<2>::from_bytes_with_nul(b"a\0").unwrap()
507                    != CString::<2>::from_bytes_with_nul(b"b\0").unwrap()
508            );
509
510            // Multiple characters
511            assert!(
512                CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
513                    == CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
514            );
515            assert!(
516                CString::<3>::from_bytes_with_nul(b"ab\0").unwrap()
517                    != CString::<4>::from_bytes_with_nul(b"abc\0").unwrap()
518            );
519        }
520    }
521
522    mod ordering {
523        use super::*;
524
525        #[test]
526        fn c_string() {
527            assert_eq!(
528                CString::<1>::new().partial_cmp(&CString::<1>::new()),
529                Some(Ordering::Equal)
530            );
531            assert_eq!(
532                CString::<2>::from_bytes_with_nul(b"a\0")
533                    .unwrap()
534                    .partial_cmp(&CString::<2>::from_bytes_with_nul(b"b\0").unwrap()),
535                Some(Ordering::Less)
536            );
537            assert_eq!(
538                CString::<2>::from_bytes_with_nul(b"b\0")
539                    .unwrap()
540                    .partial_cmp(&CString::<2>::from_bytes_with_nul(b"a\0").unwrap()),
541                Some(Ordering::Greater)
542            );
543        }
544
545        #[test]
546        fn c_str() {
547            assert_eq!(c"".partial_cmp(&CString::<1>::new()), Some(Ordering::Equal));
548            assert_eq!(
549                c"a".partial_cmp(&CString::<2>::from_bytes_with_nul(b"b\0").unwrap()),
550                Some(Ordering::Less)
551            );
552            assert_eq!(
553                c"b".partial_cmp(&CString::<2>::from_bytes_with_nul(b"a\0").unwrap()),
554                Some(Ordering::Greater)
555            );
556        }
557    }
558}