diff --git a/library/alloc/src/ffi/c_str.rs b/library/alloc/src/ffi/c_str.rs index 11bd4c4dc1ba8..508e91d4f5d6e 100644 --- a/library/alloc/src/ffi/c_str.rs +++ b/library/alloc/src/ffi/c_str.rs @@ -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, +} + +#[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 { + 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; @@ -690,6 +720,50 @@ impl CString { }), } } + + /// Attempts to convert a [Vec]<[u8]> 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) -> Result { + 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 diff --git a/library/alloc/src/ffi/c_str/tests.rs b/library/alloc/src/ffi/c_str/tests.rs index 0b7476d5cc7b7..863e090165cc4 100644 --- a/library/alloc/src/ffi/c_str/tests.rs +++ b/library/alloc/src/ffi/c_str/tests.rs @@ -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::::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::::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";