diff --git a/CHANGELOG.md b/CHANGELOG.md index d563bc234b..5d65933181 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Added `from_bytes_truncating_at_nul` to `CString` ## [v0.9.2] 2025-11-12 diff --git a/src/c_string.rs b/src/c_string.rs index 9ec011cf1c..25d5291cd4 100644 --- a/src/c_string.rs +++ b/src/c_string.rs @@ -102,6 +102,75 @@ impl CString { Ok(string) } + /// Instantiates a [`CString`] copying from the given byte slice, until the first nul character. + /// `bytes` may contain any number of nul characters, or none at all. + /// + /// This method mimics [`CStr::from_bytes_until_nul`] with two important differences: + /// [`Self::from_bytes_truncating_at_nul`] copies the data, and it does not fail on + /// non-nul terminated data. + /// + /// Fails if the given byte slice can't fit in `N`. + /// + /// # Examples + /// You can pass a byte array with one, many, or no nul bytes as `bytes`. + /// + /// ```rust + /// use heapless::CString; + /// + /// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey there!\0").unwrap(); + /// assert_eq!(c_string.as_c_str(), c"hey there!"); + /// + /// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey\0there\0!").unwrap(); + /// assert_eq!(c_string.as_c_str(), c"hey"); + /// + /// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey there!").unwrap(); + /// assert_eq!(c_string.as_c_str(), c"hey there!"); + /// ``` + /// + /// If `bytes` is too long, an error is returned. + /// ```rust + /// use heapless::CString; + /// + /// assert!(CString::<3>::from_bytes_truncating_at_nul(b"hey\0").is_err()); + /// ``` + /// + /// `bytes` may also represent an empty string. + /// ```rust + /// use heapless::CString; + /// + /// assert_eq!(CString::<1>::from_bytes_truncating_at_nul(b"").unwrap().as_c_str(), c""); + /// assert_eq!(CString::<1>::from_bytes_truncating_at_nul(b"\0").unwrap().as_c_str(), c""); + /// ``` + pub fn from_bytes_truncating_at_nul(bytes: &[u8]) -> Result { + // Truncate `bytes` to before the first nul byte. + // `bytes` will not contain any nul bytes. + let bytes = match CStr::from_bytes_with_nul(bytes) { + Ok(_) => &bytes[..bytes.len() - 1], // bytes.len() > 0, as `bytes` is nul-terminated + Err(FromBytesWithNulError::InteriorNul { position }) => &bytes[..position], + Err(FromBytesWithNulError::NotNulTerminated) => bytes, + }; + + let mut string = Self::new(); + if let Some(capacity) = string.capacity_with_bytes(bytes) { + // Cannot store `bytes` due to insufficient capacity. + if capacity > N { + return Err(CapacityError.into()); + } + } + + // SAFETY: + // `string` is left in a valid state because + // the appended bytes do not contain any nul bytes, + // and we push a nul byte at the end. + // + // We've ensured above that there is enough space to push `bytes` + // and the nul byte. + unsafe { string.extend_from_bytes_unchecked(bytes) }?; + unsafe { string.inner.push_unchecked(0) }; + + Ok(string) + } + /// Builds a [`CString`] copying from a raw C string pointer. /// /// # Safety