Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
69 changes: 69 additions & 0 deletions src/c_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,75 @@ impl<const N: usize, LenT: LenType> CString<N, LenT> {
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<Self, ExtendError> {
// 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
Expand Down