Maybeuninit

93. MaybeUninit Array Conversions — Build Fixed Arrays Without transmute

Ever tried to build a [T; N] element-by-element and ended up reaching for mem::transmute because [MaybeUninit<T>; N] refused to convert? Rust 1.95 stabilises safe conversions between [MaybeUninit<T>; N] and MaybeUninit<[T; N]> — no transmute, no tricks.

The old pain

You want a fully-initialised [T; N], but T isn’t Default (or the init is fallible, or expensive). The canonical pattern is:

  1. Allocate [MaybeUninit<T>; N] uninitialised.
  2. Fill each slot.
  3. Get a [T; N] out the other end.

Step 3 is where it got ugly. MaybeUninit::assume_init only works on MaybeUninit<T>, not [MaybeUninit<T>; N]. To flip the array-of-uninits into uninit-of-array, you reached for mem::transmute — which works, but leans on layout assumptions and carries a big “here be dragons” vibe.

The fix: From conversions

Rust 1.95 stabilises both directions of the conversion, so no transmute is needed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use std::mem::MaybeUninit;

fn main() {
    let arr: [MaybeUninit<u32>; 4] = [
        MaybeUninit::new(1),
        MaybeUninit::new(2),
        MaybeUninit::new(3),
        MaybeUninit::new(4),
    ];

    // [MaybeUninit<T>; N] -> MaybeUninit<[T; N]>
    let packed: MaybeUninit<[u32; 4]> = MaybeUninit::from(arr);
    let init: [u32; 4] = unsafe { packed.assume_init() };

    assert_eq!(init, [1, 2, 3, 4]);
}

MaybeUninit::from takes the array-of-uninits and gives you an uninit-of-array ready to assume_init. Safe, obvious, no layout assumption on your part.

The reverse direction

You can also go the other way — from MaybeUninit<[T; N]> back to [MaybeUninit<T>; N] — useful when you want to touch elements individually:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::mem::MaybeUninit;

fn main() {
    let packed: MaybeUninit<[u32; 3]> = MaybeUninit::new([10, 20, 30]);

    // MaybeUninit<[T; N]> -> [MaybeUninit<T>; N]
    let unpacked: [MaybeUninit<u32>; 3] = <[MaybeUninit<u32>; 3]>::from(packed);

    let values: [u32; 3] = unsafe {
        [
            unpacked[0].assume_init(),
            unpacked[1].assume_init(),
            unpacked[2].assume_init(),
        ]
    };
    assert_eq!(values, [10, 20, 30]);
}

Plus AsRef / AsMut for free

The same release adds AsRef<[MaybeUninit<T>; N]> and AsMut<[MaybeUninit<T>; N]> (plus slice versions) for MaybeUninit<[T; N]>. That means you can borrow the uninit array as a slice without any conversion ceremony:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use std::mem::MaybeUninit;

fn fill_evens(buf: &mut [MaybeUninit<u32>]) {
    for (i, slot) in buf.iter_mut().enumerate() {
        slot.write(i as u32 * 2);
    }
}

fn main() {
    let mut packed: MaybeUninit<[u32; 4]> = MaybeUninit::uninit();

    // AsMut<[MaybeUninit<T>]> — borrow as a mutable slice of slots
    fill_evens(packed.as_mut());

    let init: [u32; 4] = unsafe { packed.assume_init() };
    assert_eq!(init, [0, 2, 4, 6]);
}

The helper writes through the AsMut slice view; the caller gets a fully-initialised [u32; 4] after assume_init. No transmute, no pointer casting.

When to reach for it

This is niche — you don’t need it until you’re writing generic collection code, FFI wrappers, or allocator-like APIs that build arrays without Default. But when you do, the new conversions delete an uncomfortable transmute from your codebase and make the intent explicit.

Stabilised in Rust 1.95 (April 2026).