Skip to content

Commit e79f79c

Browse files
authored
Merge pull request #633 from johann-cm/from_bytes_until_nul
implement a pendant to `Cstr::from_bytes_until_nul`
2 parents 75192be + 6839106 commit e79f79c

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
66
and this project adheres to [Semantic Versioning](http://semver.org/).
77

88
## [Unreleased]
9+
- Added `from_bytes_truncating_at_nul` to `CString`
910

1011
## [v0.9.2] 2025-11-12
1112

src/c_string.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,75 @@ impl<const N: usize, LenT: LenType> CString<N, LenT> {
102102
Ok(string)
103103
}
104104

105+
/// Instantiates a [`CString`] copying from the given byte slice, until the first nul character.
106+
/// `bytes` may contain any number of nul characters, or none at all.
107+
///
108+
/// This method mimics [`CStr::from_bytes_until_nul`] with two important differences:
109+
/// [`Self::from_bytes_truncating_at_nul`] copies the data, and it does not fail on
110+
/// non-nul terminated data.
111+
///
112+
/// Fails if the given byte slice can't fit in `N`.
113+
///
114+
/// # Examples
115+
/// You can pass a byte array with one, many, or no nul bytes as `bytes`.
116+
///
117+
/// ```rust
118+
/// use heapless::CString;
119+
///
120+
/// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey there!\0").unwrap();
121+
/// assert_eq!(c_string.as_c_str(), c"hey there!");
122+
///
123+
/// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey\0there\0!").unwrap();
124+
/// assert_eq!(c_string.as_c_str(), c"hey");
125+
///
126+
/// let c_string = CString::<11>::from_bytes_truncating_at_nul(b"hey there!").unwrap();
127+
/// assert_eq!(c_string.as_c_str(), c"hey there!");
128+
/// ```
129+
///
130+
/// If `bytes` is too long, an error is returned.
131+
/// ```rust
132+
/// use heapless::CString;
133+
///
134+
/// assert!(CString::<3>::from_bytes_truncating_at_nul(b"hey\0").is_err());
135+
/// ```
136+
///
137+
/// `bytes` may also represent an empty string.
138+
/// ```rust
139+
/// use heapless::CString;
140+
///
141+
/// assert_eq!(CString::<1>::from_bytes_truncating_at_nul(b"").unwrap().as_c_str(), c"");
142+
/// assert_eq!(CString::<1>::from_bytes_truncating_at_nul(b"\0").unwrap().as_c_str(), c"");
143+
/// ```
144+
pub fn from_bytes_truncating_at_nul(bytes: &[u8]) -> Result<Self, ExtendError> {
145+
// Truncate `bytes` to before the first nul byte.
146+
// `bytes` will not contain any nul bytes.
147+
let bytes = match CStr::from_bytes_with_nul(bytes) {
148+
Ok(_) => &bytes[..bytes.len() - 1], // bytes.len() > 0, as `bytes` is nul-terminated
149+
Err(FromBytesWithNulError::InteriorNul { position }) => &bytes[..position],
150+
Err(FromBytesWithNulError::NotNulTerminated) => bytes,
151+
};
152+
153+
let mut string = Self::new();
154+
if let Some(capacity) = string.capacity_with_bytes(bytes) {
155+
// Cannot store `bytes` due to insufficient capacity.
156+
if capacity > N {
157+
return Err(CapacityError.into());
158+
}
159+
}
160+
161+
// SAFETY:
162+
// `string` is left in a valid state because
163+
// the appended bytes do not contain any nul bytes,
164+
// and we push a nul byte at the end.
165+
//
166+
// We've ensured above that there is enough space to push `bytes`
167+
// and the nul byte.
168+
unsafe { string.extend_from_bytes_unchecked(bytes) }?;
169+
unsafe { string.inner.push_unchecked(0) };
170+
171+
Ok(string)
172+
}
173+
105174
/// Builds a [`CString`] copying from a raw C string pointer.
106175
///
107176
/// # Safety

0 commit comments

Comments
 (0)