Skip to content

Add example showing UB for uninit Copy types in MaybeUninit::assume_init docs#153030

Closed
bp7968h wants to merge 1 commit intorust-lang:mainfrom
bp7968h:docs-issue-150689
Closed

Add example showing UB for uninit Copy types in MaybeUninit::assume_init docs#153030
bp7968h wants to merge 1 commit intorust-lang:mainfrom
bp7968h:docs-issue-150689

Conversation

@bp7968h
Copy link

@bp7968h bp7968h commented Feb 23, 2026

This PR adds an explicit example and explanation showing that calling assume_init() on uninitialized memory is undefined behavior even for simple types like integers that can hold any bit pattern.

Following up on the discussion in #150689 - waited for @Sekar-C-Mca to respond as suggested by @RalfJung.

Closes #150689

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Feb 23, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 23, 2026

r? @joboet

rustbot has assigned @joboet.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @scottmcm, libs
  • @scottmcm, libs expanded to 8 candidates
  • Random selection from Mark-Simulacrum, joboet, scottmcm

@rustbot

This comment has been minimized.

@joboet
Copy link
Member

joboet commented Feb 24, 2026

r? @RalfJung
How detailed do you think this should be? I'm sort of tempted to have this link to https://www.ralfj.de/blog/2019/07/14/uninit.html since it's just such a nice and concise explanation of why this is UB and the nasal demons that result from it.

@rustbot rustbot assigned RalfJung and unassigned joboet Feb 24, 2026
@rustbot
Copy link
Collaborator

rustbot commented Feb 24, 2026

RalfJung is not on the review rotation at the moment.
They may take a while to respond.

@RalfJung
Copy link
Member

I don't think the function level docs should be super detailed; if anything that'd be a task for the type-level docs.

///
/// let x = MaybeUninit::<u8>::uninit();
/// let x_init = unsafe { x.assume_init() }; // undefined behavior! ⚠️
/// // Even though `u8` can represent any bit pattern, reading uninitialized memory is still undefined behaviour.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// // Even though `u8` can represent any bit pattern, reading uninitialized memory is still undefined behaviour.
/// // Even though `u8` can represent any bit pattern, reading uninitialized memory is still undefined behavior.

/// // `x` had not been initialized yet, so this last line caused undefined behavior. ⚠️
/// ```
///
/// This also applies to simple types that can hold any bit pattern, like integers, boolean and so on.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is unclear what "this" refers to. Also, putting this here makes the entire discussion have a rather strange flow: the "safety" section starts with a basic invariant discussion, then moves on to discuss invariants beyond basic initialization, then has examples for that, and then suddenly moves back to basic invariants.

I would suggest just adding a sentence in the paragraph starting "It is up to the caller to guarantee" which emphasizes that this guarantee is non-trivial even for integers.

Copy link
Author

@bp7968h bp7968h Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback @RalfJung , do you think the below paragraph is sound enough?

It is up to the caller to guarantee that the `MaybeUninit<T>` really is in an initialized
state. Calling this when the content is not yet fully initialized causes immediate undefined
behavior. This is true even for types like integers that can hold any bit pattern, meaning 
reading uninitialized memory is always undefined behavior. The [type-level documentation][inv] 
contains more information about this initialization invariant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meaning
reading uninitialized memory is always undefined behavior

This is not correct, there are types (and parts of types) where uninit memory is fine, such as if T (the inner type) is itself MaybeUninit, or if the byte is a padding byte.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @RalfJung , I think this information is worth mentioning explicitly if byte is a padding byte and T (the inner type) is itself MaybeUninit are the only ones where uninit memory. However, if there are more it's still worth stating them all or point a docs what those types are.

Copy link
Member

@RalfJung RalfJung Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no full list the details are not set in stone yet. And I don't think this here is the place for such a list. What is important for the reader to understand is whether and where uninitialized bytes are allowed depends on the type at which the memory is read, and furthermore the integer types do not allow uninitialized bytes (but other types might).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure will update it thank you for the guidance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is up to the caller to guarantee that the `MaybeUninit<T>` really is in an initialized
state. Calling this when the content is not yet fully initialized causes immediate undefined
behavior. This includes numeric types, even though they can hold any bit pattern 
once initialized, reading uninitialized memory at a numeric type is undefined behavior.
The [type-level documentation][inv] contains more information about this initialization invariant.

@RalfJung , does this look fine, I used "numeric types" instead of "integers" to include floats (f32, f64) as well, since they also exhibit this behavior.

use std::mem::MaybeUninit;

fn main() {
    let x = MaybeUninit::<f32>::uninit();
    let _ = unsafe { x.assume_init() };
}
   Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.16s
     Running `/playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo-miri runner target/miri/x86_64-unknown-linux-gnu/debug/playground`
error: Undefined Behavior: reading memory at alloc179[0x0..0x8], but memory is uninitialized at [0x0..0x8], and this operation requires initialized memory
 --> src/main.rs:5:22
  |
5 |     let _ = unsafe { x.assume_init() };
  |                      ^^^^^^^^^^^^^^^ Undefined Behavior occurred here
  |
  = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
  = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

Uninitialized memory occurred at alloc179[0x0..0x8], in this allocation:
alloc179 (stack variable, size: 8, align: 8) {
    __ __ __ __ __ __ __ __                         │ ░░░░░░░░
}

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

Copy link
Member

@RalfJung RalfJung Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a short way to explain what we have to explain, and the long way is already in the type-level docs. So really what we have to achieve here is getting people to read those docs, there's no way around that. I'd suggest something like this as a new paragraph after "It is up to the caller"

It is a common mistake to assume that this function is safe to call on integers because they can hold all bit patterns. It is also a common mistake to think that calling this function is UB if any byte is uninitialized. Both of these assumptions are wrong. If that is surprising to you, please read the type-level documentation.

Honestly it's probably easier for me to make a PR than to indirectly edit the docs by suggesting changes to you. And anyway I shouldn't approve the PR if I am the one who authored the wording. So I'll make a PR with my proposal. Updating the documentation of tricky unsafe functions is one of the hardest kinds of things to get right so it's not really a great way to get started contributing to the compiler I am afraid. Sorry that you were misled by the "easy" label on that issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR with my proposed changes -- I ended up having to edit the type-level docs as well to really make this work: #153570

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @RalfJung, will look into other issues cheers, closing this in favor of PR #153570.

@bp7968h bp7968h closed this Mar 9, 2026
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docs for MaybeUninit::assume_init should indicate that reading uninit POD isn't sound

5 participants