Question on uninitalized memory

The docs mention that the following is undefined behaviour:

let b: bool = unsafe { MaybeUninit::uninit().assume_init() }; // undefined behavior!

Is this really always undefined, even when b is never read or written to? If so, what's the reason for that? What could break in this example?
And if something like this is not UB, would it be okay to set the value to some initialized state with std::ptr::write and then use it?

EDIT: corrected to std::ptr::write

Creating a bool with a non-true/false state is UB. It's an invariant that must be upheld, and creating a book that is not either true or false is UB.

But let's say you did do this, and prayed the compiler didn't do anything about it. If you're referring to std::ptr::drop_in_place then that's exactly what we're trying to avoid. One of biggest things that people overlook when writing unsafe code involving improperly managed uninitialized values is when the destructors are run. It's not so much the existence of the value, it's when it's secretly read and used.

let my_string: String = unsafe { std::mem::uninit() };

Is not UB, as long as you call std::men::forget on it. Since the String is in an invalid state, and you now must pass ownership of it to forget, there's really no point of doing this. Sure, you could argue that std::ptr::write is viable for just writing into here, but you could just as easily have used std::mem::MaybeUninit as intended and completely disregard this mess.


You ask "what could break in this example", and that's the very nature of UB. You don't know what could break. It's undefined what will happen when you do this and therefore the compiler is free to break your code as it sees fit since it's not within its contract. UB isn't a matter of how your code will be broken it's a matter of if your code could be broken, and the fact that it might.

Whoops, i actually meant std::ptr::write, edited the original post.

So my example wouldn't be UB anymore if i added

std::mem::forget(b);

or

std::ptr::write(&mut b, true);

?

This is also UB, std::mem::uninitialized() is pretty much always UB, unless you are creating a zero-sized type or MaybeUninit<_>. see uninit's crate docs for a more detailed writeup by @Yandros.

std::mem::forget does nothing if your type has no drop glue. If you want to work with uninitialized memory, you must use MaybeUninit<T> and raw pointers.

2 Likes

See this succinct explanation of UB by @alice.

It doesn't really matter whether a value is read or written to.

A bool can only ever have the bit patterns 0x01 and 0x00, whereas you're creating a bool from uninitialized memory (which can have any possible bit pattern). By merely creating a bool using MaybeUninit::uninit().assume_init() you're violating bool's invariants and invoking undefined behaviour.

You may also like to read RalfJung's "What The Hardware Does" is not What Your Program Does: Uninitialized Memory. They've written several really good articles about how Rust reasons about memory and undefined behaviour.

Thank you everyone for your help!

That was a very informative article, thanks.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.