Skip to content

add CString::from_vec_until_nul #96186

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
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
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.
Comment on lines +728 to +730
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be good to mention that this doesn't reduce the capacity of the Vec/CString. (Like the Vec::truncate docs.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait, never mind: CString stores a Box<[u8]>, not a Vec, so it does shrink the capacity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does raise the question of how useful this method is, though, since it most likely ends up reallocating anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the same thing could be accomplished with Cstr::from_bytes_with_nul() following by .into_c_string(). But being able to do the same thing from a CString context would be a kindness. Particularly if the user doesn't think to check the docs for both places when looking for this functionality.

///
/// # 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
39 changes: 39 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,45 @@ 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![];
assert_eq!(CString::from_vec_until_nul(v), Err(FromVecUntilNulError { bytes: vec![] }));

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

// Test an empty nul-terminated string
let v = vec![0u8];
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![0u8, 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