|
| 1 | +# Uninitialized memory |
| 2 | + |
| 3 | +> _"I'm Nobody! Who are you? Are you — Nobody — too?"_ |
| 4 | +> |
| 5 | +> — _Emily Dickinson_ |
| 6 | +
|
| 7 | +While we have covered [invalid values], there's another thing that behaves a lot like invalid values, but has nothing to do with actual bit patterns: Uninitialized memory. |
| 8 | + |
| 9 | +An easy way to think about uninitialized memory is that there's an additional value (often called `undef` using LLVM's term for it) that does not map to any concrete bit pattern, but can be introduced in abstract in various ways, and makes _most_ values invalid. |
| 10 | + |
| 11 | +If you explicitly wish to work with uninitialized and partially-initialized types, [`MaybeUninit<T>`] is a useful abstraction since it can be "initialized" with no overhead and then written to in parts. |
| 12 | + |
| 13 | +## Sources of uninitialized memory |
| 14 | + |
| 15 | +### `mem::uninitialized()` and `MaybeUninit::assume_init` |
| 16 | + |
| 17 | +[`mem::uninitialized()`] is a deprecated API that has a very tempting shape, it lets you do things like `let x = mem::uninitialized()` for cases when you want to construct the value in bits. It's basically _always_ UB to use, since it immediately sets `x` to uninitialized memory, which is UB. |
| 18 | + |
| 19 | +Use [`MaybeUninit<T>`] instead. |
| 20 | + |
| 21 | +It is still possible to create uninitialized memory using [`MaybeUninit::assume_init()`] if you have not, in fact, assured that things are initialized. |
| 22 | + |
| 23 | +### Padding |
| 24 | + |
| 25 | +Padding bytes in structs and enums are often but not always uninitialized. This means that treating a struct as a bag of bytes (by, say, treating `&Struct` as `&[u8; size_of::<Struct>()]` and reading from there) is UB even if you don't write invalid values to those bytes, since you are ginning up uninitialized `u8`s. |
| 26 | + |
| 27 | +Reading from padding [always produces uninitialized values][pad-glossary]. |
| 28 | + |
| 29 | + |
| 30 | + |
| 31 | +### Moved-from values |
| 32 | + |
| 33 | +The following code is UB: |
| 34 | + |
| 35 | +```rust |
| 36 | +let x = Foo::new(); // Foo is not Copy |
| 37 | +let mut v = vec![]; |
| 38 | +let ptr = &x as *const Foo; |
| 39 | + |
| 40 | +v.push(x); // move x into the vector |
| 41 | + |
| 42 | +unsafe { |
| 43 | + // reads from moved-from memory |
| 44 | + let ghost = ptr::read(x); |
| 45 | +} |
| 46 | +``` |
| 47 | + |
| 48 | +Any type of move will do this, even when you "move" the value into a different variable with stuff like `let y = x;`. |
| 49 | + |
| 50 | +Note that Rust does let you "partially move" out of fields of a struct, in such a case the whole struct is now no longer a valid value for its type, but you are still allowed to "use" the struct to look at other fields. When doing such things, make sure there are no pointers that still think the struct is whole and valid. |
| 51 | + |
| 52 | +#### APIs that are not moves: `ptr::drop_in_place()`, `ManuallyDrop::drop()`, and `ptr::read()` |
| 53 | + |
| 54 | +[`ptr::drop_in_place()`] and [`ManuallyDrop::drop()`] are interesting: they call all the destructor[^1] on a value (or a pointed-to value in the case of the former). From a safety point of view they are identical; they are just different APIs for dealing with manually calling drop glue. |
| 55 | + |
| 56 | +[`ManuallyDrop::drop()`] makes the following claim: |
| 57 | + |
| 58 | +> Other than changes made by the destructor itself, the memory is left unchanged, and so as far as the compiler is concerned still holds a bit-pattern which is valid for the type T. |
| 59 | +
|
| 60 | +In other words, Rust does _not_ consider these operations to do the same invalidation as a regular "move from" operation, even though they have a similar feel. |
| 61 | + |
| 62 | +There is an [open issue][ugc-394] about whether `Drop::drop()` is itself allowed to produce uninitialized or invalid memory, so it may not be possible to rely on this in a generic context. |
| 63 | + |
| 64 | +[`ptr::read()`] similarly claims that it leaves the source memory untouched, which means that it is still a valid value. |
| 65 | + |
| 66 | + |
| 67 | +For all of these APIs, actually _using_ the dropped or read-from memory may still be fraught depending on the invariants of the value; it's quite easy to cause a double-free by materializing an owned value from the original data after it has already been read-from or dropped. |
| 68 | + |
| 69 | +However, they do not produce uninitialized memory. |
| 70 | + |
| 71 | + |
| 72 | +### Freshly allocated memory |
| 73 | + |
| 74 | +Freshly allocated memory (e.g. the yet-unused bytes in [`Vec::with_capacity()`] or just the result of [`Allocator::allocate()`]) is usually uninitialized. You can use APIs like [`Allocator::zeroed()`] if you wish to avoid this, though you can still end up making [invalid values] the same way you can with [`mem::zeroed()`]. |
| 75 | + |
| 76 | +Generally after allocating memory one should make sure that the only part of that memory being read from is known to have been written to. This can be tricky in situations around @@@ |
| 77 | + |
| 78 | +## When you might end up making an uninitialized value |
| 79 | + |
| 80 | +## Things you might see if you made an uninitialized value |
| 81 | + |
| 82 | + |
| 83 | + [invalid values]: ../core_unsafety/invalid_values.md |
| 84 | + [`mem::uninitialized()`]: https://doc.rust-lang.org/stable/std/mem/fn.uninitialized.html |
| 85 | + [`mem::zeroed()`]: https://doc.rust-lang.org/stable/std/mem/fn.zeroed.html |
| 86 | + [`MaybeUninit<T>`]: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html |
| 87 | + [`MaybeUninit::assume_init()`]: https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#method.assume_init |
| 88 | + [pad-glossary]: https://github.com/rust-lang/unsafe-code-guidelines/blob/master/reference/src/glossary.md#padding |
| 89 | + [`ptr::drop_in_place()`]: https://doc.rust-lang.org/stable/std/ptr/fn.drop_in_place.html |
| 90 | + [`ManuallyDrop::drop()`]: https://doc.rust-lang.org/stable/std/mem/struct.ManuallyDrop.html#method.drop |
| 91 | + [`ptr::read()`]: https://doc.rust-lang.org/stable/std/ptr/fn.read.html |
| 92 | + [ugc-394]: https://github.com/rust-lang/unsafe-code-guidelines/issues/394 |
| 93 | + [`Vec::with_capacity()`]: https://doc.rust-lang.org/stable/std/vec/struct.Vec.html#method.with_capacity |
| 94 | + [`Allocator::allocate()`]: https://doc.rust-lang.org/stable/std/alloc/trait.Allocator.html#tymethod.allocate |
| 95 | + [`Allocator::zeroed()`]: https://doc.rust-lang.org/stable/std/alloc/trait.Allocator.html#method.allocate_zeroed |
| 96 | + |
| 97 | + |
| 98 | + [^1]: The "destructor" is different from the `Drop` trait. Calling the destructor is the process of calling a type's `Drop::drop` impl if it exists, and then calling the destructor for all of its fields (also known as "drop glue"). I.e. it's not _just_ `Drop`, but rather the entire _destruction_, of which the destructor is one part. Types that do not implement `Drop` may still have contentful destructors if their transitive fields do. |
| 99 | + |
0 commit comments