Description
Simplest repro:
use std::ffi::CString;
let vec = Vec::with_capacity(87);
let cstring = CString::new(vec);
let vec = cstring.into_bytes();
assert_eq!(vec.capacity(), 87);
I expected CString
to behave like String
, preserving capacity from the Vec
it was created. Instead, the CString
implementation shrinks-to-fit and stores a boxed slice, which is then converted back into Vec
in into_bytes
/into_bytes_with_nul
, giving back as much capacity as there were elements, i.e. 1 which is the nul terminator.
I vaguely understand the reasoning for this — when &CStr
becomes a narrow pointer, Box<CStr>
would be a narrow pointer too, meaning that CString
also will; in such a case, if CString
was a Vec
instead, it would be three pointers in size (pointer, size, capacity). This means, however, that there's no reason to use CString
instead of Box<CStr>
, making it essentially useless.
In other words, the only reason for String
/OsString
/CString
to exist in parallel with Box<str>
/Box<OsStr>
/Box<CStr>
is to provide the same safety guarantees for the contents while also providing Vec
-like functionality (growing/shrinking and spare capacity). In reality, CString
does not provide those at all, much like OsString
(though it'd be considerably more complex to implement push
/pop
for OsString
on Windows even if it's a Vec
, since that'd mean that encoding and decoding the underlying WTF-8 would be required).
As an initial effort, OsString
and CString
can be transformed into Vec
s transparently to their public APIs. Then, the Vec
API can be integrated via the usual RFC process.
Meta
rustc --version --verbose
:
rustc 1.46.0-nightly (3503f565e 2020-07-02)
binary: rustc
commit-hash: 3503f565e1fb7296983757d2716346f48a4a262b
commit-date: 2020-07-02
host: x86_64-unknown-linux-gnu
release: 1.46.0-nightly
LLVM version: 10.0