`<[T]>::as_uninit_mut`

Some decoders may accept an uninitialized out buffer.

fn decode<'s, 'd>(
    src: &'s [u8],
    dst: &'d mut [MaybeUninit<u8>]
) -> Result<&'d mut [u8], Error>;

If we add this method:

impl<T> [T] {
    pub fn as_uninit_mut(&mut self) -> &mut [MaybeUninit<T>] {
        let (ptr, len) = (self.as_mut_ptr(), self.len());
        unsafe { core::slice::from_raw_parts_mut(ptr.cast(), len) }
    }
}

Then we can easily pass an initialized buffer to the decoder.

let mut buf = vec![0; len];
let result = decode(src, buf.as_uninit_mut());

I like this idea - I had the same one for ndarray, it would be convenient.

I ended up not implementing it because it has a hole that makes it unsafe unfortunately:

let mut data = [0; 4];
data.as_uninit_mut()[0] = MaybeUninit::uninit();
// when we reach this point `data[0]` is uninitialized in safe code
7 Likes

Oh that's right. &mut T is invariant over T. I should read the Rustonomicon once again.

It's unfortunate, we need another abstraction - not MaybeUninit - that's compatible with write-valid-only, not sure how to do that :slight_smile: i.e a type that represents an uninitialized hole but the only valid operation is to fill the hole.

I'm not sure if this would handle what you're trying to do, but you might be interested in https://rust-lang.github.io/rfcs/2930-read-buf.html#reference-level-explanation.

3 Likes

I can't speak to vec specifically, but for simple array use cases and especially when inlining is working, examining the godbolt output for this sort of pattern shows no difference between using an initialized array e.g. [0u8; 4] or unsafe { mem::zeroed() }:

If the output bytes are overwritten, LLVM appears to optimize away initially filling the buffer with zeroes.

I mention this mostly because I see people unnecessarily reach for unsafe here for assumed performance benefits when the generated code is identical to using safe code.

If you have some wacky FFI use case, obviously all bets are off.

4 Likes