Skip to content

Commit 23fd684

Browse files
committed
add CString::from_vec_until_nul
This adds a member fn that converts a Vec into a CString; it is intended to more useful than from_vec_with_nul (which requires that the caller already know where the nul byte is). This is a companion to CStr::from_bytes_until_nul, and shares the feature gate: cstr_from_bytes_until_nul It reuses the error type from from_vec_with_nul, which may or may not be the right choice.
1 parent 6fd7e90 commit 23fd684

File tree

2 files changed

+116
-0
lines changed

2 files changed

+116
-0
lines changed

library/alloc/src/ffi/c_str.rs

+74
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,36 @@ impl FromVecWithNulError {
214214
}
215215
}
216216

217+
/// An error indicating that a nul byte was not found.
218+
///
219+
/// The vector passed to [`CString::from_vec_until_nul`] must have at
220+
/// least one nul byte present.
221+
///
222+
#[derive(Clone, PartialEq, Eq, Debug)]
223+
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
224+
pub struct FromVecUntilNulError {
225+
bytes: Vec<u8>,
226+
}
227+
228+
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
229+
impl FromVecUntilNulError {
230+
/// Returns a `u8` slice containing the bytes that were attempted to convert
231+
/// to a [`CString`].
232+
#[must_use]
233+
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
234+
pub fn as_bytes(&self) -> &[u8] {
235+
&self.bytes[..]
236+
}
237+
238+
/// Returns ownership of the bytes that were attempted to convert
239+
/// to a [`CString`].
240+
#[must_use = "`self` will be dropped if the result is not used"]
241+
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
242+
pub fn into_bytes(self) -> Vec<u8> {
243+
self.bytes
244+
}
245+
}
246+
217247
/// An error indicating invalid UTF-8 when converting a [`CString`] into a [`String`].
218248
///
219249
/// `CString` is just a wrapper over a buffer of bytes with a nul terminator;
@@ -690,6 +720,50 @@ impl CString {
690720
}),
691721
}
692722
}
723+
724+
/// Attempts to convert a <code>[Vec]<[u8]></code> to a [`CString`].
725+
///
726+
/// The input [`Vec`] must contain at least one nul byte.
727+
///
728+
/// If the nul byte is not at the end of the input `Vec`, then the `Vec`
729+
/// will be truncated so that there is only one nul byte, and that byte
730+
/// is at the end.
731+
///
732+
/// # Errors
733+
///
734+
/// If no nul byte is present, an error will be returned.
735+
///
736+
/// # Examples
737+
/// ```
738+
/// #![feature(cstr_from_bytes_until_nul)]
739+
///
740+
/// use std::ffi::CString;
741+
///
742+
/// let mut buffer = vec![0u8; 16];
743+
/// unsafe {
744+
/// // Here we might call an unsafe C function that writes a string
745+
/// // into the buffer.
746+
/// let buf_ptr = buffer.as_mut_ptr();
747+
/// buf_ptr.write_bytes(b'A', 8);
748+
/// }
749+
/// // Attempt to extract a C nul-terminated string from the buffer.
750+
/// let c_str = CString::from_vec_until_nul(buffer).unwrap();
751+
/// assert_eq!(c_str.into_string().unwrap(), "AAAAAAAA");
752+
/// ```
753+
///
754+
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
755+
pub fn from_vec_until_nul(mut v: Vec<u8>) -> Result<Self, FromVecUntilNulError> {
756+
let nul_pos = memchr::memchr(0, &v);
757+
match nul_pos {
758+
Some(nul_pos) => {
759+
v.truncate(nul_pos + 1);
760+
// SAFETY: We know there is a nul byte at nul_pos, so this slice
761+
// (ending at the nul byte) is a well-formed C string.
762+
Ok(unsafe { Self::_from_vec_with_nul_unchecked(v) })
763+
}
764+
None => Err(FromVecUntilNulError { bytes: v }),
765+
}
766+
}
693767
}
694768

695769
// Turns this `CString` into an empty string to prevent

library/alloc/src/ffi/c_str/tests.rs

+42
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,48 @@ fn cstr_from_bytes_until_nul() {
142142
assert_eq!(r.to_bytes(), b"");
143143
}
144144

145+
#[test]
146+
fn cstring_from_vec_until_nul() {
147+
// Test an empty vec. This should fail because it
148+
// does not contain a nul byte.
149+
let v = Vec::from(b"");
150+
assert_eq!(CString::from_vec_until_nul(v), Err(FromVecUntilNulError { bytes: Vec::from(b"") }));
151+
152+
// Test a non-empty vec, that does not contain a nul byte.
153+
let v = Vec::from(b"hello");
154+
assert_eq!(
155+
CString::from_vec_until_nul(v),
156+
Err(FromVecUntilNulError { bytes: Vec::from(b"hello") })
157+
);
158+
159+
// Test an empty nul-terminated string
160+
let v = Vec::from(b"\0");
161+
let r = CString::from_vec_until_nul(v).unwrap();
162+
assert_eq!(r.into_bytes(), b"");
163+
164+
// Test a vec with the nul byte in the middle (and some excess capacity)
165+
let mut v = Vec::<u8>::with_capacity(20);
166+
v.extend_from_slice(b"hello\0world!");
167+
let r = CString::from_vec_until_nul(v).unwrap();
168+
assert_eq!(r.into_bytes(), b"hello");
169+
170+
// Test a vec with the nul byte at the end (and some excess capacity)
171+
let mut v = Vec::<u8>::with_capacity(20);
172+
v.extend_from_slice(b"hello\0");
173+
let r = CString::from_vec_until_nul(v).unwrap();
174+
assert_eq!(r.into_bytes(), b"hello");
175+
176+
// Test a vec with two nul bytes at the end
177+
let v = Vec::from(b"hello\0\0");
178+
let r = CString::from_vec_until_nul(v).unwrap();
179+
assert_eq!(r.into_bytes(), b"hello");
180+
181+
// Test a vec containing lots of nul bytes
182+
let v = Vec::from(b"\0\0\0\0");
183+
let r = CString::from_vec_until_nul(v).unwrap();
184+
assert_eq!(r.into_bytes(), b"");
185+
}
186+
145187
#[test]
146188
fn into_boxed() {
147189
let orig: &[u8] = b"Hello, world!\0";

0 commit comments

Comments
 (0)