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}