Skip to content

Commit 2de237a

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
1 parent 2a4b00b commit 2de237a

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-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

+39
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,45 @@ 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![];
150+
assert_eq!(CString::from_vec_until_nul(v), Err(FromVecUntilNulError { bytes: vec![] }));
151+
152+
// Test a non-empty vec ("hello"), that does not contain a nul byte.
153+
let v = Vec::from(&b"hello"[..]);
154+
assert_eq!(CString::from_vec_until_nul(v.clone()), Err(FromVecUntilNulError { bytes: v }));
155+
156+
// Test an empty nul-terminated string
157+
let v = vec![0u8];
158+
let r = CString::from_vec_until_nul(v).unwrap();
159+
assert_eq!(r.into_bytes(), b"");
160+
161+
// Test a vec with the nul byte in the middle (and some excess capacity)
162+
let mut v = Vec::<u8>::with_capacity(20);
163+
v.extend_from_slice(b"hello\0world!");
164+
let r = CString::from_vec_until_nul(v).unwrap();
165+
assert_eq!(r.into_bytes(), b"hello");
166+
167+
// Test a vec with the nul byte at the end (and some excess capacity)
168+
let mut v = Vec::<u8>::with_capacity(20);
169+
v.extend_from_slice(b"hello\0");
170+
let r = CString::from_vec_until_nul(v).unwrap();
171+
assert_eq!(r.into_bytes(), b"hello");
172+
173+
// Test a vec with two nul bytes at the end
174+
let v = Vec::from(&b"hello\0\0"[..]);
175+
let r = CString::from_vec_until_nul(v).unwrap();
176+
assert_eq!(r.into_bytes(), b"hello");
177+
178+
// Test a vec containing lots of nul bytes
179+
let v = vec![0u8, 0, 0, 0];
180+
let r = CString::from_vec_until_nul(v).unwrap();
181+
assert_eq!(r.into_bytes(), b"");
182+
}
183+
145184
#[test]
146185
fn into_boxed() {
147186
let orig: &[u8] = b"Hello, world!\0";

0 commit comments

Comments
 (0)