MaybeUninit vs Option

So far, it seems to me like MaybeUninit is like a more flexible version of Option in the sense that you can swap the value in the union via MaybeUninit::write(...). Instead of unwrapping, you call MaybeUninit::assume_init(). However, this is all of course unsafe, as the union is allocated to store std::mem::sizeof::<T>() bytes with zero-cost abstraction (thanks to #repr transparent) but there is no guarantee that T is yet set in that allocation space. Do any of you have any experience with writing code using MaybeUninit and would like to share your understanding of it that builds or corrects what I've laid-down so-far?

Usually you don't need to use MaybeUninit. The major use case for it is compact data structures/primitives and ffi (esp with C). Option is almost always good enough and as compact as MaybeUninit due to null pointer optimization.
If you need to use MaybeUninit, then you likely are already writing some nontrivial unsafe code.

5 Likes

To add on, MaybeUninit is more like an Option for each and every field in a type, without actually changing the fields of the type. For example:

struct Foo(u32, u64);
unsafe {
    let mut bar = MaybeUninit::<Foo>::uninit();
    bar.value.0 = 20;
    //What does this do?
    println!("{}", bar.value.1);
}

So it is useful for building structures piece by piece, and not all at once.

1 Like

Well, the data at bar.value.1 is allocated memory. I don't think allocators zero-out by default (for performance reasons), thus, the data therein is "leftover" from a previous allocation. A reference theretowards is sort of like a hazard pointer.

infinitely many possibilities!

AKA UB, especially with non-trivial types such as &'a _/*[const|mut] _ and Unique<T> (Many types contain a Unique<T> which include Vec, String and Box<T>)

1 Like

That's really all there is to it, IMO. You use Option unless you're really, really sure you can't afford the extra storage (which might not even exist) and checks (which can also just be mitigated in other ways, like making things #[cold] or using unreachable_unchecked).

1 Like

Note that stack allocation is cheap, especially add 1 byte slot(with some alignment padding though) on the stack is just next to no-op. The only real difference Option<T> make is that it automatically adds flag check on every read/write/drop, which must be exist at some point of the code. Otherwise use-after-free, double-free, uninitialized read, and the whole UB realm of C/++'s sin will swallow your mind.