Skip to content

Commit

Permalink
add CString::from_vec_until_nul
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ericseppanen committed May 18, 2022
1 parent 6fd7e90 commit 23fd684
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 0 deletions.
74 changes: 74 additions & 0 deletions library/alloc/src/ffi/c_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,36 @@ impl FromVecWithNulError {
}
}

/// An error indicating that a nul byte was not found.
///
/// The vector passed to [`CString::from_vec_until_nul`] must have at
/// least one nul byte present.
///
#[derive(Clone, PartialEq, Eq, Debug)]
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
pub struct FromVecUntilNulError {
bytes: Vec<u8>,
}

#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
impl FromVecUntilNulError {
/// Returns a `u8` slice containing the bytes that were attempted to convert
/// to a [`CString`].
#[must_use]
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
pub fn as_bytes(&self) -> &[u8] {
&self.bytes[..]
}

/// Returns ownership of the bytes that were attempted to convert
/// to a [`CString`].
#[must_use = "`self` will be dropped if the result is not used"]
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
}

/// An error indicating invalid UTF-8 when converting a [`CString`] into a [`String`].
///
/// `CString` is just a wrapper over a buffer of bytes with a nul terminator;
Expand Down Expand Up @@ -690,6 +720,50 @@ impl CString {
}),
}
}

/// Attempts to convert a <code>[Vec]<[u8]></code> to a [`CString`].
///
/// The input [`Vec`] must contain at least one nul byte.
///
/// If the nul byte is not at the end of the input `Vec`, then the `Vec`
/// will be truncated so that there is only one nul byte, and that byte
/// is at the end.
///
/// # Errors
///
/// If no nul byte is present, an error will be returned.
///
/// # Examples
/// ```
/// #![feature(cstr_from_bytes_until_nul)]
///
/// use std::ffi::CString;
///
/// let mut buffer = vec![0u8; 16];
/// unsafe {
/// // Here we might call an unsafe C function that writes a string
/// // into the buffer.
/// let buf_ptr = buffer.as_mut_ptr();
/// buf_ptr.write_bytes(b'A', 8);
/// }
/// // Attempt to extract a C nul-terminated string from the buffer.
/// let c_str = CString::from_vec_until_nul(buffer).unwrap();
/// assert_eq!(c_str.into_string().unwrap(), "AAAAAAAA");
/// ```
///
#[unstable(feature = "cstr_from_bytes_until_nul", issue = "95027")]
pub fn from_vec_until_nul(mut v: Vec<u8>) -> Result<Self, FromVecUntilNulError> {
let nul_pos = memchr::memchr(0, &v);
match nul_pos {
Some(nul_pos) => {
v.truncate(nul_pos + 1);
// SAFETY: We know there is a nul byte at nul_pos, so this slice
// (ending at the nul byte) is a well-formed C string.
Ok(unsafe { Self::_from_vec_with_nul_unchecked(v) })
}
None => Err(FromVecUntilNulError { bytes: v }),
}
}
}

// Turns this `CString` into an empty string to prevent
Expand Down
42 changes: 42 additions & 0 deletions library/alloc/src/ffi/c_str/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,48 @@ fn cstr_from_bytes_until_nul() {
assert_eq!(r.to_bytes(), b"");
}

#[test]
fn cstring_from_vec_until_nul() {
// Test an empty vec. This should fail because it
// does not contain a nul byte.
let v = Vec::from(b"");
assert_eq!(CString::from_vec_until_nul(v), Err(FromVecUntilNulError { bytes: Vec::from(b"") }));

// Test a non-empty vec, that does not contain a nul byte.
let v = Vec::from(b"hello");
assert_eq!(
CString::from_vec_until_nul(v),
Err(FromVecUntilNulError { bytes: Vec::from(b"hello") })
);

// Test an empty nul-terminated string
let v = Vec::from(b"\0");
let r = CString::from_vec_until_nul(v).unwrap();
assert_eq!(r.into_bytes(), b"");

// Test a vec with the nul byte in the middle (and some excess capacity)
let mut v = Vec::<u8>::with_capacity(20);
v.extend_from_slice(b"hello\0world!");
let r = CString::from_vec_until_nul(v).unwrap();
assert_eq!(r.into_bytes(), b"hello");

// Test a vec with the nul byte at the end (and some excess capacity)
let mut v = Vec::<u8>::with_capacity(20);
v.extend_from_slice(b"hello\0");
let r = CString::from_vec_until_nul(v).unwrap();
assert_eq!(r.into_bytes(), b"hello");

// Test a vec with two nul bytes at the end
let v = Vec::from(b"hello\0\0");
let r = CString::from_vec_until_nul(v).unwrap();
assert_eq!(r.into_bytes(), b"hello");

// Test a vec containing lots of nul bytes
let v = Vec::from(b"\0\0\0\0");
let r = CString::from_vec_until_nul(v).unwrap();
assert_eq!(r.into_bytes(), b"");
}

#[test]
fn into_boxed() {
let orig: &[u8] = b"Hello, world!\0";
Expand Down

0 comments on commit 23fd684

Please sign in to comment.