Skip to content

Commit 0f9bfe7

Browse files
committed
Add section on uninitialized memory
1 parent d35f558 commit 0f9bfe7

File tree

4 files changed

+102
-4
lines changed

4 files changed

+102
-4
lines changed

src/SUMMARY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44
- [Undefined behavior](./undefined_behavior.md)
55
- [Core unsafety](./core_unsafety.md)
66
- [Dangling and unaligned pointers](./core_unsafety/dangling_and_unaligned_pointers.md)
7+
- [Invalid values](./core_unsafety/invalid_values.md)
78
- [Data races](./core_unsafety/data_races.md)
89
- [Intrinsics](./core_unsafety/intrinsics.md)
910
- [ABI and FFI](./core_unsafety/abi_and_ffi.md)
1011
- [Platform features](./core_unsafety/platform_features.md)
1112
- [Inline assembly](./core_unsafety/inline_assembly.md)
1213
- [Advanced unsafety](./advanced_unsafety.md)
13-
- [Invalid values](./core_unsafety/invalid_values.md)
14+
- [Uninitialized memory](./advanced_unsafety/uninitialized.md)
1415
- [Pointer aliasing](./advanced_unsafety/pointer_aliasing.md)
1516
- [Immutable data](./advanced_unsafety/immutable_data.md)
1617
- [Atomic ordering](./advanced_unsafety/atomic_ordering.md)
17-
- [Undef memory](./advanced_unsafety/undef_memory.md)
1818
- [Pinning](./advanced_unsafety/pinning.md)
1919
- [Variance](./advanced_unsafety/variance.md)
2020
- [Expert unsafety](./expert_unsafety.md)

src/advanced_unsafety/invalid_values.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ This is not an exhaustive list: ultimately, having an invalid value is UB and it
129129

130130

131131
[unaligned]: ../core_unsafety/dangling_and_unaligned_pointers.md
132-
[uninit-chapter]: ../undef_memory.md
132+
[uninit-chapter]: ../advanced_unsafe/uninitialized.md
133133
[`mem::transmute()`]: https://doc.rust-lang.org/stable/std/mem/fn.transmute.html
134134
[`mem::transmute_copy()`]: https://doc.rust-lang.org/stable/std/mem/fn.transmute_copy.html
135135
[`mem::zeroed()`]: https://doc.rust-lang.org/stable/std/mem/fn.zeroed.html

src/advanced_unsafety/undef_memory.md

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
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

Comments
 (0)