Overview
The idea is that when a -sys
crate requires a &::std::ffi::CStr
as input, most rust wrappers chose to require a &str
and then call CString::new()
which always clones the slice.
This becomes obnoxious when the conversions happen back and forth, even though after the first time we know there is a terminating null byte.
On top of that, sometimes the string may contain a null byte, in which case the CString
construction fails, even though truncation could be acceptable.
So I suggest adding the following API to the ::std
lib, which only clones the given data when it contains no null byte, and with “optimal” perfomance (the string is not scanned multiple times).
It’s all here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5cecbed40e6755d003d72f7069c89a05
Documentation example
impl str {
fn to_c_string_lossy(&'_ self) -> Cow<'_, CStr>
Converts a string slice into a valid C string slice.
Since the returned C string slice must uphold C strings invariant,
-
the conversion may be lossy since the returned C string slice will be truncated up to the first encountered null byte of the input string.
-
if no null byte is found, then the whole input string slice is copied with an appended null byte.
Hence the Cow<CStr>
.
Complexity-wise, the string is always scanned once, up to the first encountered null byte (or the end of the string if none was found). If it contains no null bytes, it is then copied into the heap. In other words, its complexity is optimal when there is no information regarding the contained null bytes of the input string slice.
Examples
Basic usage
// a string slice with a null byte terminator
let hello_world = "Hello, world!\0";
// This just scans the string and casts it.
let c_hello_world = hello_world.to_c_string_lossy();
assert_eq!(
c_hello_world.to_bytes_with_nul(),
b"Hello, world!\0",
);
Appending the null byte if needed.
// a string slice with no null bytes
let hello_world = "Hello, world!";
// Since the string contains no null bytes, it is copied and mutated.
let c_hello_world = hello_world.to_c_string_lossy();
assert_eq!(
c_hello_world.to_bytes_with_nul(),
b"Hello, world!\0",
);
“Incorrect” interior null byte:
// a string slice containing an interior null byte
let hello_world = "Hell\0, world!";
let c_hello_world = hello_world.to_c_string_lossy();
assert_eq!(
c_hello_world.to_bytes(),
b"Hell", // This is indeed *lossy* !
);