UB doesn’t occupy the author’s mind when writing C, when it really should. This kind of lazy attitude to memory safety is precisely why so much C code is notoriously riddled with memory bugs and security vulnerabilities.
UB doesn’t occupy the author’s mind when writing C, when it really should. This kind of lazy attitude to memory safety is precisely why so much C code is notoriously riddled with memory bugs and security vulnerabilities.
It's not as painless as it could be though, because many of the MaybeUninit<T> -> T conversion fns are unstable. Eg the code in TFA needs `&mut [MaybeUninit<T>] -> &mut [T]` but `[T]::assume_init_mut()` is unstable. But reimplementing them is just a matter of copying the libstd impl, that in turn is usually just a straightforward reinterpret-cast one-liner.
There are two actual differences in this regard: C pointers are more ergonomic than Rust pointers. And Rust has an additional feature called references, which enable a lot more aggressive compiler optimizations, but which have the restriction that you can’t have a reference to uninitialized memory.
let mut data = Vec::with_capacity(sz);
unsafe { data.set_len(sz) };
buf.copy_to_slice(data.as_mut_slice());
is UB?Currently, the team is leaning in the direction of not requiring recursive validity for references. This would mean your code is not language UB as long as you can assume `set_len` and `copy_to_slice` never read from 'data`. However, it's still considered library UB, as this assumption is not documented or specified anywhere and is not guaranteed -- changes to safe code in your program or in the standard library can turn this into language UB, so by doing something like this you're writing fragile code that gives up a lot of Rust's safety by design.
let mut data = Vec::with_capacity(sz);
data.extend(&buf[..sz]);
Vec::extend extends a container from an iterable. A Vec/slice is iterable.And from the doc:
> This implementation is specialized for slice iterators, where it uses copy_from_slice to append the entire slice at once.
Of course this trivial example could also be written as:
let mut data = buf.clone();It’s strictly more complicated and slower than the obvious thing to do and only exists to satisfy the abstract machine.
let mut data = Vec::with_capacity(sz);
let mut dst_uninit = data.spare_capacity_mut();
let uninit_src: &[MaybeUninit<T>] = unsafe { transmute(buf) };
dst_uninit.copy_from_slice(uninit_src);
unsafe { data.set_len(sz) };UB is weird and valgrind is not a tool for detecting UB. For that you want Miri or UBSAN. Valgrind’s equivalent is ASAN and MSAN which catch UB issues incidentally in some rare cases and not necessarily where the UB actually happened.
It sounds like the more difficult problem here has to do with explaining to the compiler that read() is not being used unsafely.
Most other UBs related to datums that you think you can do something with.
size_t convert(state_t* state, const void* inp, void* out)
This function now works with both initialized and uninitialized data in practice. It also is transparent over whether the output buffer is an `u8` (a byte buffer to write it out into a `File`) or `u16` (a buffer for then using the UTF16). I've never had to think about whether this doesn't work (in this particular context; let's ignore any alignment concerns for writes into `out` in this example) and I don't recall running into any issues writing such code in a long long time.If I write the equivalent code in Rust I may write
fn convert(&mut self, inp: &[u8], out: &mut [MaybeUninit<u8>]) -> usize
The problem is now obvious to me, but at least my intention is clear: "Come here! Give me your uninitialized arrays! I don't care!". But this is not the end of the problem, because writing this code is theoretically unsafe. If you have a `[u8]` slice for `out` you have to convert it to `[MaybeUninit<u8>]`, but then the function could theoretically write uninitialized data and that's UB isn't it? So now I have to think about this problem and write this instead: fn convert(&mut self, inp: &[u8], out: &mut [u8]) -> usize
...and that will also be unsafe, because now I have to convert my actual `[MaybeUninit<u8>]` buffer (for file writes) to `[u8]` for calls to this API.Long story short, this is a problem that occupies my mind when writing in Rust, but not in C. That doesn't mean that C's many unsafeties don't worry me, it just means that this _particular_ problem type described above doesn't come up as an issue in C code that I write.
Edit: Also, what usefulcat said.
Something like:
fn convert<'i, 'o>(inp: &'i [u8], buf: &'o mut MaybeUninit<u8>) -> &'o mut [u8]
(Honest question, actually… because the above may be impossible to write and I’m on my phone and can’t try it.)Edit: it works: https://play.rust-lang.org/?version=stable&mode=debug&editio...
Edit: Also, I believe your code would fail my second section, as the `convert` function would have difficulty accepting a `[u8]` slice. Converting `[u8]` to `[MaybeUninit<u8>]` is not safe per se.
But I don’t think this is really a shortcoming, so much as a simple consequence of strong typing. If you want take “whatever” as a parameter, you have to spell out the types that satisfy it, whether it’s via a trait, or an enum with specific variants, etc. You don’t get to just cast things to void and hope for the best, and still call the result safe.