From 92a9d3e587d138d92831760d42095e1465a74d69 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Sun, 22 Feb 2015 01:50:23 +0200 Subject: [PATCH] Switch to std::ffi::CStr Harmonization with the changes in std::ffi landed as the result of https://github.com/rust-lang/rfcs/pull/592 --- src/c_str.rs | 384 +++++++++++++++++-------------------------------- tests/bench.rs | 18 +-- tests/tests.rs | 130 +++++++---------- 3 files changed, 188 insertions(+), 344 deletions(-) diff --git a/src/c_str.rs b/src/c_str.rs index 08594d9..3e542e7 100644 --- a/src/c_str.rs +++ b/src/c_str.rs @@ -39,10 +39,10 @@ //! //! # Borrowed C strings //! -//! Both `OwnedCString` and `CStrBuf` dereference to `CStr`, a token type -//! that asserts the C string requirements when passed or returned -//! by reference. `&CStr` can be used to encapsulate FFI functions under a -//! safe facade. +//! Both `OwnedCString` and `CStrBuf` dereference to `std::ffi::CStr`, an +//! unsized type that asserts the C string requirements when passed or +//! returned by reference. `&CStr` can be used to encapsulate FFI functions +//! under a safe facade. //! //! An example of creating and using a C string would be: //! @@ -52,7 +52,8 @@ //! extern crate c_string; //! extern crate libc; //! -//! use c_string::{CStr, CStrBuf}; +//! use c_string::CStrBuf; +//! use std::ffi::CStr; //! //! fn safe_puts(s: &CStr) { //! unsafe { libc::puts(s.as_ptr()) }; @@ -76,20 +77,22 @@ #![feature(core)] #![feature(io)] #![feature(libc)] +#![feature(std_misc)] extern crate libc; use std::cmp::Ordering; use std::error::{Error, FromError}; +use std::ffi::CStr; use std::fmt; +use std::fmt::Debug; use std::hash; use std::io::Error as IoError; use std::io::ErrorKind::InvalidInput; +use std::iter::IntoIterator; use std::marker; use std::mem; use std::ops::Deref; -use std::slice; -use std::str; const NUL: u8 = 0; @@ -179,24 +182,31 @@ impl OwnedCString { } } -impl fmt::Debug for OwnedCString { +impl Debug for OwnedCString { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - (**self).fmt(f) + write!(f, "\"{}\"", escape_bytestring(self.to_bytes())) } } /// Error information about a failed string conversion due to an interior NUL /// in the source data. -#[derive(Copy, Debug)] pub struct NulError { - - /// The offset at which the first NUL occurs. - position: usize + position: usize, + bytes: Vec } impl NulError { + + /// Returns the position of the first NUL byte in the byte sequence that + /// was attempted to convert to `CStrBuf`. pub fn nul_position(&self) -> usize { self.position } + + /// Consumes this error and returns the bytes that were attempted to make + /// a C string with. + pub fn into_bytes(self) -> Vec { + self.bytes + } } impl Error for NulError { @@ -212,50 +222,18 @@ impl fmt::Display for NulError { } } -impl FromError for IoError { - fn from_error(err: NulError) -> IoError { - IoError::new(InvalidInput, "invalid data for C string: contains a NUL byte", - Some(format!("NUL at position {}", err.position))) - } -} - -/// A possible error value from the `CStrBuf::from_vec` function. -#[derive(Debug)] -pub struct IntoCStrError { - cause: NulError, - bytes: Vec -} - -impl IntoCStrError { - - /// Access the `NulError` that is the cause of this error. - pub fn nul_error(&self) -> NulError { - self.cause - } - - /// Consume this error, returning the bytes that were attempted to make - /// a C string with. - pub fn into_bytes(self) -> Vec { - self.bytes - } -} - -impl Error for IntoCStrError { - - fn description(&self) -> &str { - self.cause.description() - } -} - -impl fmt::Display for IntoCStrError { +impl fmt::Debug for NulError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.cause) + // TODO: truncate output if too long + write!(f, "NulError {{ position: {}, bytes: \"{}\" }}", + self.position, escape_bytestring(&self.bytes)) } } -impl FromError for IoError { - fn from_error(err: IntoCStrError) -> IoError { - FromError::from_error(err.nul_error()) +impl FromError for IoError { + fn from_error(err: NulError) -> IoError { + IoError::new(InvalidInput, "invalid data for C string: contains a NUL byte", + Some(format!("NUL at position {}", err.position))) } } @@ -271,50 +249,15 @@ enum CStrData { /// /// Values of this type can be obtained by conversion from Rust strings and /// byte slices. +/// +/// This type serves the same purpose as `std::ffi::CString`, but provides +/// in-place optimization for small strings and different ergonomics in the +/// ways `CStrBuf` values can be constructed. #[derive(Clone)] pub struct CStrBuf { data: CStrData } -/// A type to denote null-terminated string data borrowed under a reference. -/// -/// `CStr` is only used by reference, e.g. as a parameter in a safe function -/// proxying its FFI counterpart. -#[repr(packed)] -pub struct CStr { - chars: [libc::c_char] -} - -fn bytes_into_c_str(s: &[u8]) -> CStrBuf { - let mut out = CStrBuf { - data: unsafe { blank_str_data() } - }; - if !copy_in_place(s, &mut out.data) { - out = vec_into_c_str(s.to_vec()); - } - out -} - -#[inline] -unsafe fn blank_str_data() -> CStrData { - CStrData::InPlace(mem::uninitialized()) -} - -fn copy_in_place(s: &[u8], out: &mut CStrData) -> bool { - let len = s.len(); - if len >= IN_PLACE_SIZE { - return false; - } - match *out { - CStrData::InPlace(ref mut buf) => { - slice::bytes::copy_memory(buf, s); - buf[len] = 0; - } - _ => unreachable!() - } - true -} - fn vec_into_c_str(mut v: Vec) -> CStrBuf { v.push(NUL); CStrBuf { data: CStrData::Owned(v) } @@ -356,28 +299,96 @@ macro_rules! c_str { // literal out of bytestring or string literals. Otherwise, we could // use from_static_bytes and accept byte strings as well. // See https://github.com/rust-lang/rfcs/pull/566 - $crate::CStr::from_static_str(concat!($lit, "\0")) + unsafe { + std::ffi::CStr::from_ptr(concat!($lit, "\0").as_ptr() + as *const libc::c_char) + } } } impl CStrBuf { - /// Create a `CStrBuf` by copying a byte slice. + /// Create a `CStrBuf` by consuming an iterable source of bytes. /// /// # Failure /// - /// Returns `Err` if the byte slice contains an interior NUL byte. - pub fn from_bytes(s: &[u8]) -> Result { - if let Some(pos) = s.position_elem(&NUL) { - return Err(NulError { position: pos }); + /// Returns `Err` if the source contains an interior NUL byte. + pub fn from_iter(iterable: I) -> Result + where I: IntoIterator + { + fn nul_error(mut collected: Vec, remaining: I) -> NulError + where I: Iterator + { + let pos = collected.len(); + collected.push(NUL); + collected.extend(remaining); + NulError { position: pos, bytes: collected } } - Ok(bytes_into_c_str(s)) - } - /// Create a `CStrBuf` by copying a byte slice, - /// without checking for interior NUL characters. - pub unsafe fn from_bytes_unchecked(s: &[u8]) -> CStrBuf { - bytes_into_c_str(s) + let mut iter = iterable.into_iter(); + let mut vec: Vec = match iter.size_hint() { + (_, Some(len)) if len < IN_PLACE_SIZE => { + // The iterator promises the items will fit into the + // in-place variant. + let mut buf: [u8; IN_PLACE_SIZE] + = unsafe { mem::uninitialized() }; + for i in 0 .. len + 1 { + match iter.next() { + Some(NUL) => { + return Err(nul_error(buf[.. i].to_vec(), iter)); + } + Some(c) => { + buf[i] = c; + } + None => { + buf[i] = NUL; + return Ok(CStrBuf { + data: CStrData::InPlace(buf) + }); + } + } + } + // The upper bound on iterator length was a lie. + // Copy the collected buffer into the vector + // that the owned collection path will continue with + let mut vec = Vec::::with_capacity(len + 2); + vec.extend(buf[.. len + 1].iter().cloned()); + vec + } + (lower, _) => { + Vec::with_capacity(lower + 1) + } + }; + // Loop invariant: vec.len() < vec.capacity() + loop { + match iter.next() { + None => { + break; + } + Some(NUL) => { + return Err(nul_error(vec, iter)); + } + Some(c) => { + let len = vec.len(); + unsafe { + *vec.get_unchecked_mut(len) = c; + vec.set_len(len + 1); + } + if vec.len() == vec.capacity() { + // Get capacity for some more iterations and continue + vec.reserve(1); + } + } + } + } + { + let len = vec.len(); + unsafe { + *vec.get_unchecked_mut(len) = NUL; + vec.set_len(len + 1); + } + } + Ok(CStrBuf { data: CStrData::Owned(vec) }) } /// Create a `CStrBuf` by copying a string slice. @@ -387,14 +398,7 @@ impl CStrBuf { /// Returns `Err` if the string contains an interior NUL character. #[inline] pub fn from_str(s: &str) -> Result { - CStrBuf::from_bytes(s.as_bytes()) - } - - /// Create a `CStrBuf` by copying a string slice, - /// without checking for interior NUL characters. - #[inline] - pub unsafe fn from_str_unchecked(s: &str) -> CStrBuf { - CStrBuf::from_bytes_unchecked(s.as_bytes()) + CStrBuf::from_iter(s.as_bytes().iter().cloned()) } /// Consumes a byte vector to create `CStrBuf`, taking care to avoid @@ -404,12 +408,9 @@ impl CStrBuf { /// /// If the given vector contains a NUL byte, then an error containing /// the original vector and `NulError` information is returned. - pub fn from_vec(vec: Vec) -> Result { - if let Some(pos) = vec[..].position_elem(&NUL) { - return Err(IntoCStrError { - cause: NulError { position: pos }, - bytes: vec - }); + pub fn from_vec(vec: Vec) -> Result { + if let Some(pos) = vec.position_elem(&NUL) { + return Err(NulError {position: pos, bytes: vec}); } Ok(vec_into_c_str(vec)) } @@ -421,12 +422,12 @@ impl CStrBuf { /// Converts `self` into a byte vector, potentially saving an allocation. pub fn into_vec(mut self) -> Vec { - let mut data = unsafe { blank_str_data() }; - mem::swap(&mut self.data, &mut data); - match data { + match mem::replace(&mut self.data, + CStrData::InPlace(unsafe { mem::uninitialized() })) + { CStrData::Owned(mut v) => { - let len = v.len() - 1; - v.truncate(len); + let len = v.len(); + v.truncate(len - 1); v } CStrData::InPlace(ref a) => { @@ -449,137 +450,6 @@ impl fmt::Debug for CStrBuf { } } -impl CStr { - - /// Constructs a `CStr` reference from a raw pointer to a - /// null-terminated string. - /// - /// This function is unsafe because there is no guarantee of the validity - /// of the pointer `raw` or a guarantee that a NUL terminator will be found. - /// Also there are no compile-time checks whether the lifetime as inferred - /// is a suitable lifetime for the returned slice. - /// - /// # Caveat - /// - /// The lifetime of the returned reference is inferred from its usage. - /// This may be incorrect in many cases. Consider this example: - /// - /// ```no_run - /// #[macro_use] - /// extern crate c_string; - /// - /// extern crate libc; - /// extern "C" { - /// fn strdup(source: *const libc::c_char) -> *mut libc::c_char; - /// } - /// - /// use c_string::CStr; - /// - /// fn main() { - /// let ptr = unsafe { strdup(c_str!("Hello!").as_ptr()) }; - /// - /// let s = unsafe { CStr::from_ptr(ptr).to_bytes() }; - /// - /// unsafe { libc::free(ptr as *mut libc::c_void) }; - /// - /// let guess_what = s[0]; - /// // The lifetime of s is inferred to extend to the line above - /// } - /// ``` - /// - /// To prevent accidental misuse, the lifetime should be restricted - /// in some way. This can be a helper function or method taking the - /// lifetime from a 'host' value, when available. In other cases, explicit - /// annotation may be used. - #[inline] - pub unsafe fn from_ptr<'a>(raw: *const libc::c_char) -> &'a CStr { - let inner = slice::from_raw_parts(raw, 1); - mem::transmute(inner) - } - - /// Create a `CStr` reference out of a static byte array. - /// - /// This function can be used with null-terminated byte string literals. - /// For non-literal data, prefer `CStrBuf::from_bytes`, since that - /// constructor checks for interior NUL bytes. - /// - /// # Panics - /// - /// Panics if the byte array is not null-terminated. - pub fn from_static_bytes(bytes: &'static [u8]) -> &'static CStr { - assert!(bytes.last() == Some(&NUL), - "static byte string is not null-terminated: \"{}\"", - escape_bytestring(bytes)); - let ptr = bytes.as_ptr() as *const libc::c_char; - unsafe { CStr::from_ptr(ptr) } - } - - /// Create a `CStr` reference out of a static string. - /// - /// This function can be used with null-terminated string literals. - /// For non-literal data, prefer `CStrBuf::from_str`, since that - /// constructor checks for interior NUL characters. - /// - /// # Panics - /// - /// Panics if the string is not null-terminated. - pub fn from_static_str(s: &'static str) -> &'static CStr { - assert!(s.ends_with("\0"), - "static string is not null-terminated: \"{}\"", s); - let ptr = s.as_ptr() as *const libc::c_char; - unsafe { CStr::from_ptr(ptr) } - } - - /// Returns a raw pointer to the null-terminated C string. - /// - /// The returned pointer can only be considered valid - /// during the lifetime of the `CStr` value. - #[inline] - pub fn as_ptr(&self) -> *const libc::c_char { - self.chars.as_ptr() - } - - /// Scans the string to get a byte slice of its contents. - /// - /// The returned slice does not include the terminating NUL byte. - pub fn to_bytes(&self) -> &[u8] { - let ptr = self.as_ptr(); - unsafe { - slice::from_raw_parts(ptr as *const u8, libc::strlen(ptr) as usize) - } - } - - /// Scans the string as UTF-8 string slice. - /// - /// # Failure - /// - /// Returns `Err` with information on the conversion error if the string is - /// not valid UTF-8. - #[inline] - pub fn to_utf8(&self) -> Result<&str, str::Utf8Error> { - str::from_utf8(self.to_bytes()) - } - - /// Returns an iterator over the string's bytes. - #[inline] - pub fn iter<'a>(&'a self) -> CChars<'a> { - CChars { - ptr: self.as_ptr(), - lifetime: marker::PhantomData - } - } - - /// Returns true if the wrapped string is empty. - #[inline] - pub fn is_empty(&self) -> bool { self.chars[0] == 0 } -} - -impl fmt::Debug for CStr { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\"{}\"", escape_bytestring(self.to_bytes())) - } -} - impl Deref for CStrBuf { type Target = CStr; @@ -605,6 +475,12 @@ pub struct CChars<'a> { lifetime: marker::PhantomData<&'a [libc::c_char]>, } +impl<'a> CChars<'a> { + pub fn from_c_str(s: &'a CStr) -> CChars<'a> { + CChars { ptr: s.as_ptr(), lifetime: marker::PhantomData } + } +} + impl<'a> Iterator for CChars<'a> { type Item = libc::c_char; diff --git a/tests/bench.rs b/tests/bench.rs index dd1adae..aba76f2 100644 --- a/tests/bench.rs +++ b/tests/bench.rs @@ -17,15 +17,7 @@ extern crate test; use test::Bencher; -use c_string::{CStr, CStrBuf}; - -#[inline(always)] -fn smoke_c_str(s: &CStr, expected: &str) { - let p = s.as_ptr(); - let off = expected.len() / 2; - let c = unsafe { *p.offset(off as isize) } as u8; - assert_eq!(c, expected.as_bytes()[off]); -} +use c_string::CStrBuf; static S_SHORT: &'static str = "Mary"; static S_MEDIUM: &'static str = "Mary had a little lamb, Little lamb"; @@ -39,8 +31,7 @@ static S_LONG: &'static str = "\ fn bench_c_str_buf_from_str(b: &mut Bencher, s: &str) { b.iter(|| { - let c_str = CStrBuf::from_str(s).unwrap(); - smoke_c_str(&*c_str, s); + CStrBuf::from_str(s) }); } @@ -61,8 +52,7 @@ fn bench_c_str_buf_from_str_long(b: &mut Bencher) { fn bench_c_str_buf_from_str_unchecked(b: &mut Bencher, s: &str) { b.iter(|| { - let c_str = unsafe { CStrBuf::from_str_unchecked(s) }; - smoke_c_str(&*c_str, s); + unsafe { CStrBuf::from_vec_unchecked(s.as_bytes().to_vec()) } }); } @@ -86,7 +76,6 @@ fn bench_c_str_buf_from_vec_and_back(b: &mut Bencher, s: &str) { b.iter(move || { let vec = shelf.take().unwrap(); let c_str = CStrBuf::from_vec(vec).unwrap(); - smoke_c_str(&*c_str, s); shelf = Some(c_str.into_vec()); }); } @@ -111,7 +100,6 @@ fn bench_c_str_buf_from_vec_unchecked_and_back(b: &mut Bencher, s: &str) { b.iter(move || { let vec = shelf.take().unwrap(); let c_str = unsafe { CStrBuf::from_vec_unchecked(vec) }; - smoke_c_str(&*c_str, s); shelf = Some(c_str.into_vec()); }); } diff --git a/tests/tests.rs b/tests/tests.rs index 7b51786..a511c37 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -9,9 +9,11 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +#![feature(collections)] #![feature(core)] #![feature(io)] #![feature(libc)] +#![feature(std_misc)] #![feature(test)] #[macro_use] @@ -21,12 +23,13 @@ extern crate test; extern crate libc; use std::error::FromError; +use std::ffi::CStr; use std::io::Error as IoError; use std::io::ErrorKind::InvalidInput; use std::iter::Iterator; use std::ptr; -use c_string::{CStr, CStrBuf, OwnedCString}; +use c_string::{CStrBuf, OwnedCString, CChars}; use c_string::{libc_free, parse_c_multistring}; pub fn check_c_str(c_str: &CStr, expected: &[u8]) { @@ -96,11 +99,11 @@ fn test_owned_c_string_as_ptr() { #[test] fn test_iterator() { let c_string = str_dup(""); - let mut iter = c_string.iter(); + let mut iter = CChars::from_c_str(&c_string); assert_eq!(iter.next(), None); let c_string = str_dup("hello"); - let mut iter = c_string.iter(); + let mut iter = CChars::from_c_str(&c_string); assert_eq!(iter.next(), Some('h' as libc::c_char)); assert_eq!(iter.next(), Some('e' as libc::c_char)); assert_eq!(iter.next(), Some('l' as libc::c_char)); @@ -110,20 +113,28 @@ fn test_iterator() { } #[test] -fn test_c_str_buf_from_bytes() { - let c_str = CStrBuf::from_bytes(b"").unwrap(); - check_c_str(&c_str, b""); - - let c_str = CStrBuf::from_bytes(b"foo\xFF").unwrap(); - check_c_str(&c_str, b"foo\xFF"); - - // Owned variant - let c_str = CStrBuf::from_bytes(b"Mary had a little \xD0\x0D, Little \xD0\x0D").unwrap(); - check_c_str(&c_str, b"Mary had a little \xD0\x0D, Little \xD0\x0D"); +fn test_c_str_buf_from_iter() { + let test_strings = [ + b"", + b"foo\xFF", + b"Mary had a little \xD0\x0D, Little \xD0\x0D" // Exercises the owned variant + ]; + for bytes in test_strings.iter() { + let c_str = CStrBuf::from_iter(bytes.iter().cloned()).unwrap(); + check_c_str(&c_str, bytes); + } - let res = CStrBuf::from_bytes(b"got\0nul"); - let err = res.err().unwrap(); - assert_eq!(err.nul_position(), 3); + let test_strings = [ + b"got\0nul", + b"Mary had a little lamb, Little \0" // Exercises the owned variant + ]; + for bytes in test_strings.iter() { + let res = CStrBuf::from_iter(bytes.iter().cloned()); + let err = res.err().unwrap(); + assert_eq!(err.nul_position(), bytes.position_elem(&0u8).unwrap()); + let vec = err.into_bytes(); + assert_eq!(&vec, bytes); + } } #[test] @@ -141,6 +152,8 @@ fn test_c_str_buf_from_str() { let res = CStrBuf::from_str("got\0nul"); let err = res.err().unwrap(); assert_eq!(err.nul_position(), 3); + let bytes = err.into_bytes(); + assert_eq!(bytes, b"got\0nul"); } #[test] @@ -154,21 +167,27 @@ fn test_io_error_from_nul_error() { #[test] fn test_c_str_buf_from_vec() { - let c_str = CStrBuf::from_vec(b"".to_vec()).unwrap(); - check_c_str(&c_str, b""); - - let c_str = CStrBuf::from_vec(b"hello".to_vec()).unwrap(); - check_c_str(&c_str, b"hello"); - - // Owned variant - let c_str = CStrBuf::from_vec(b"Mary had a little lamb, Little lamb".to_vec()).unwrap(); - check_c_str(&c_str, b"Mary had a little lamb, Little lamb"); + let test_strings = [ + b"", + b"foo\xFF", + b"Mary had a little \xD0\x0D, Little \xD0\x0D" // Exercises the owned variant + ]; + for bytes in test_strings.iter() { + let c_str = CStrBuf::from_vec(bytes.to_vec()).unwrap(); + check_c_str(&c_str, bytes); + } - let res = CStrBuf::from_vec(b"got\0nul".to_vec()); - let err = res.err().unwrap(); - assert_eq!(err.nul_error().nul_position(), 3); - let vec = err.into_bytes(); - assert_eq!(&vec[..], b"got\0nul"); + let test_strings = [ + b"got\0nul", + b"Mary had a little lamb, Little \0" // Exercises the owned variant + ]; + for bytes in test_strings.iter() { + let res = CStrBuf::from_vec(bytes.to_vec()); + let err = res.err().unwrap(); + assert_eq!(err.nul_position(), bytes.position_elem(&0u8).unwrap()); + let vec = err.into_bytes(); + assert_eq!(&vec, bytes); + } } #[test] @@ -188,23 +207,19 @@ fn test_c_str_buf_into_vec() { let c_str = CStrBuf::from_str("hello").unwrap(); let vec = c_str.into_vec(); assert_eq!(&vec[..], b"hello"); - let c_str = CStrBuf::from_bytes(b"foo\xFF").unwrap(); + let bytes = b"foo\xFF"; + let c_str = CStrBuf::from_iter(bytes.iter().cloned()).unwrap(); let vec = c_str.into_vec(); - assert_eq!(&vec[..], b"foo\xFF"); + assert_eq!(&vec[..], bytes); // Owned variant let c_str = CStrBuf::from_str("Mary had a little lamb, Little lamb").unwrap(); let vec = c_str.into_vec(); assert_eq!(&vec[..], b"Mary had a little lamb, Little lamb"); - let c_str = CStrBuf::from_bytes(b"Mary had a little \xD0\x0D, Little \xD0\x0D").unwrap(); + let bytes = b"Mary had a little \xD0\x0D, Little \xD0\x0D"; + let c_str = CStrBuf::from_iter(bytes.iter().cloned()).unwrap(); let vec = c_str.into_vec(); - assert_eq!(&vec[..], b"Mary had a little \xD0\x0D, Little \xD0\x0D"); -} - -#[test] -fn test_c_str_is_empty() { - let c_str = CStrBuf::from_str("").unwrap(); - assert!(c_str.is_empty()); + assert_eq!(&vec[..], bytes); } #[test] @@ -217,29 +232,6 @@ fn test_owned_c_string_to_bytes() { assert_eq!(c_str.to_bytes(), b"foo\xFF"); } -#[test] -fn test_owned_c_string_to_utf8() { - let c_str = str_dup("hello"); - assert_eq!(c_str.to_utf8(), Ok("hello")); - let c_str = str_dup(""); - assert_eq!(c_str.to_utf8(), Ok("")); - let c_str = bytes_dup(b"foo\xFF"); - assert!(c_str.to_utf8().is_err()); -} - -#[test] -fn test_c_str_debug() { - let c_str = c_str!("hello"); - let msg = format!("{:?}", c_str); - assert_eq!(msg, "\"hello\""); - let c_str = c_str!(""); - let msg = format!("{:?}", c_str); - assert_eq!(msg, "\"\""); - let c_str = CStr::from_static_bytes(b"foo\xFF\0"); - let msg = format!("{:?}", c_str); - assert_eq!(msg, r#""foo\xff""#); -} - #[test] fn test_owned_c_string_debug() { let c_str = str_dup("hello"); @@ -260,15 +252,3 @@ fn test_c_string_new_fail() { OwnedCString::new(ptr::null(), libc_free) }; } - -#[test] -#[should_fail] -fn test_from_static_bytes_fail() { - let _c_str = CStr::from_static_bytes(b"no nul"); -} - -#[test] -#[should_fail] -fn test_from_static_str_fail() { - let _c_str = CStr::from_static_str("no nul"); -}