[][src]Crate uninit

::uninit

A collection of tools for a safer usage of uninitialized memory.

Latest version Documentation License


Many crates out there try to replicate C "optimization" patterns such as handling uninitialized memory without taking into account that Rust types carry semantics much more subtle than those of C.

For instance, the following code is Undefined Behavior!

use ::core::mem;

let mut array: [u8; 256] = unsafe { mem::uninitialized() };
for (i, x) in array.iter_mut().enumerate() {
    *x = i as u8;
}

Indeed, it creates u8s with uninitialized memory, which currently has no defined behavior in the Rust model (see "What The Hardware Does" is not What Your Program Does: Uninitialized Memory, by Ralf Jung), and then creates Rust references to these invalid u8s, which become in turn invalid too.

Do not use mem::uninitialized!

In hindsight, offering a mem::uninitialized function in the core library of Rust (even if it was marked unsafe), has been of the worst mistakes of the language. Indeed, the function is generic and only bounded by Sized, and it turns out that, except for zero-sized types or the later introduced MaybeUninit<T>, all the other calls to this function are unsound (instant UB).

Note that there are other ways to trigger this UB without explicitely using mem::uninitialized::<T>(), such as:

  • use ::core::mem::MaybeUninit;
    type T = u8; // for the example
    
    unsafe {
        MaybeUninit::<T>::uninit() // Uninitialized `MaybeUninit<T>` => Fine
            .assume_init()         // Back to an uninitialized `T` => UB
    };
    • this is exactly equivalent to calling mem::uninitialized::<T>(),

    • yes, using MaybeUninit is more subtle than just changing a function call.

  • let mut vec: Vec<u8> = Vec::with_capacity(100); // Fine
    unsafe {
        vec.set_len(100); // UB: we have an uninitialized [u8; 100] in the heap
    }

Instead, (you can) use MaybeUninit

So, the solution to manipulating uninitialized memory is to use MaybeUninit: the special type MaybeUninit<T> does not assume that its backing memory has been initialized / the behavior of an uninitialized MaybeUninit<T> is well-defined, no matter the T.

How to correctly use MaybeUninit

It is all about the delayed initialization pattern:

  1. Creation

    A MaybeUninit<T> is created, with, for instance, MaybeUninit::<T>::uninit():

    use ::core::mem::MaybeUninit;
    
    let mut x = MaybeUninit::<i32>::uninit();
  2. (Delayed) Initialization

    With great care to avoid accidentally creating (even if only for an instant) a &T, &mut T, or even a T while the memory has not been initialized yet (which would be UB), we can write to (and thus initialize) the uninitialized memory through a &mut MaybeUninit<T>:

    • either directly, for instance:

      use ::core::mem::MaybeUninit;
      let mut x = MaybeUninit::<i32>::uninit();
      
      x = MaybeUninit::new(42);
    • or through a raw *mut T pointer (contrary to Rust references, raw pointers do not assume that the memory they point to is valid). For instance:

      use ::core::mem::MaybeUninit;
      let mut x = MaybeUninit::<i32>::uninit();
      
      unsafe {
          x.as_mut_ptr().write(42);
      }
  3. Type-level upgrade

    Once we know, for sure, that the memory has been initialized, we can upgrade the MaybeUninit<T> type to the fully-fledged T type:

The problem

As you can see, manipulating MaybeUninit to initialize its contents is done through restrictive and unergonomic types (&mut MaybeUninit<T> / *mut T).

So most APIs do not offer a way to output / write into uninitialized memory.

This is what ends up leading many people to do the step .3 before the step .2: it is oh so much ergonomic to work with a &mut T than a *mut T, especially when arrays, slices and vectors are involved. Thus people end up doing UB.

One of the worst offenders of this situation is the Read trait

use ::std::io;

pub trait Read {
    fn read (&mut self, buf: &mut [u8]) -> Result<usize, io::Error>;
    // ...
}

that is, there is no way to .read() into an unitialized buffer (it would require an api taking either a (*mut u8, usize) pair, or, equivalently and by the way more ergonomically, a &mut [MaybeUninit<u8>]).

Enter ::uninit

So, the objective of this crate is double:

Status

This is currently at an realy stage, so it "only" includes utilities to work with uninitialized bytes or integers.

Macros

uninit_byte_array

Sets up an inline / stack-allocated array of uninitialized bytes.

Traits

AsUninitSliceMut

Basically AsMut<[MaybeUninit<u8>]> but with a more explicit name.

InitWithCopyFromSlice

Extension trait for [MaybeUninit<u8>], that initializes the buffer with a copy from another (already initialized) buffer.

MaybeUninitExt

Extension trait providing tranformations between init and uninit.

ReadIntoUninit

Trait for a Readable type that can output the bytes read into uninitialised memory.

VecExtendFromReader

Extension trait for Vec, that grows the vec by a bounded amount of bytes, obtained when reading from R.

VecReserveUninit

Extension trait for Vec, that reserves extra uninitialized memory for it, and returns a mutable handle on those extra (uninitialized) bytes.