contact: use my ETH email address
[web.git] / personal / _posts / 2020-07-15-unused-data.md
1 ---
2 title: "Why even unused data needs to be valid"
3 categories: rust
4 forum: https://internals.rust-lang.org/t/why-even-unused-data-needs-to-be-valid/12734
5 ---
6
7 The Rust compiler has a few assumptions that it makes about the behavior of all code.
8 Violations of those assumptions are referred to as [Undefined Behavior][ub].
9 Since Rust is a safe-by-default language, programmers usually do not have to worry about those rules (the compiler and libraries ensure that safe code always satisfies the assumptions),
10 but authors of `unsafe` code are themselves responsible for upholding these requirements.
11
12 Those assumptions are [listed in the Rust reference](https://doc.rust-lang.org/reference/behavior-considered-undefined.html).
13 The one that seems to be most surprising to many people is the clause which says that Rust code may not *produce* "[...] an invalid value, even in private fields and locals".
14 The reference goes on to explain that "*producing* a value happens any time a value is assigned to or read from a place, passed to a function/primitive operation or returned from a function/primitive operation".
15 In other words, even just *constructing*, for example, an invalid `bool`, is Undefined Behavior---no matter whether that `bool` is ever actually "used" by the program.
16 The purpose of this post is to explain why that rule is so strict.
17
18 [ub]: https://rust-lang.github.io/unsafe-code-guidelines/glossary.html#undefined-behavior
19
20 <!-- MORE -->
21
22 First of all, let me clarify what is meant by "used" here, as that term is used to mean very different things.
23 The following code "uses" `b`:
24
25 {% highlight rust %}
26 fn example(b: bool) -> i32 {
27   if b { 42 } else { 23 }
28 }
29 {% endhighlight %}
30
31 I hope it is not very surprising that calling `example` on, e.g., `3` transmuted to `bool` is Undefined Behavior (UB).
32 When compiling `if`, the compiler assumes that `0` and `1` are the only possible values; there is no saying what could go wrong when that assumption is violated.
33 For example, the compiler might use a [jump table](https://en.wikipedia.org/wiki/Branch_table); an out-of-bounds index in that table could literally execute any code, so there is no way to bound the behavior in that case.
34 (This is a compiler-understood *validity invariant* that is fixed in the language specification, which is very different from a user-defined *safety invariant*.
35 See [this earlier post]({% post_url 2018-08-22-two-kinds-of-invariants %}) for more details on that distinction.)
36
37 What is less obvious is why calling `example` on `3` is UB even when there is no such `if` being executed.
38 To understand why that is important, let us consider the following example:
39
40 {% highlight rust %}
41 fn example(b: bool, num: u32) -> i32 {
42   let mut acc = 0;
43   for _i in 0..num {
44     acc += if b { 42 } else { 23 };
45   }
46   acc
47 }
48 {% endhighlight %}
49
50 Now assume we were working in a slightly different version of Rust, where transmuting `3` to a `bool` is fine as long as you do not "use" the `bool`.
51 That would mean that calling `example(transmute(3u8), 0)` is actually allowed, because in that case the loop never gets executed, so we never "use" `b`.
52
53 However, this is a problem for a very important transformation called [loop-invariant code motion](https://en.wikipedia.org/wiki/Loop-invariant_code_motion).
54 That transformation can be used to turn our `example` function into the following:
55
56 {% highlight rust %}
57 fn example(b: bool, num: u32) -> i32 {
58   let mut acc = 0;
59   let incr = if b { 42 } else { 23 };
60   for _i in 0..num {
61     acc += incr;
62   }
63   acc
64 }
65 {% endhighlight %}
66
67 The increment `if b { 42 } else { 23 }`, now called `incr`, is "invariant" during the execution of the loop, and thus computing the increment can be moved out.
68 Why is this a good transformation?
69 Instead of determining the increment each time around the loop, we do that just once, thus saving a lot of conditional jumps that the CPU is unhappy about.
70 This also enables further transformations down the road, e.g. the compiler could notice that this is just `num*incr`.
71
72 However, in our hypothetical Rust where "unused" values may be invalid, this important optimization is actually incorrect!
73 To see why, consider again calling `example(transmute(3u8), 0)`.
74 Before the optimization, that call was fine.
75 After the optimization, that call is UB because we are doing `if b` where `b` is `3`.
76
77 This is because loop-invariant code motion makes dead code live when the loop is not actually executed.
78 (We can think of this as a form of "speculative execution", though entirely unrelated to CPU-level speculative execution.)
79 To fix the optimization, we could require the compiler to prove that the loop runs at least once (i.e., we could avoid speculative execution), but in general that will be hard (and in `example` it is impossible, since `num` can indeed be `0`).
80 Another option is to restructure the code to only compute `incr` if `num > 0`, but again this can be hard to do in general.
81 So the alternative that Rust uses is to require that even "unused" data satisfies some basic validity.
82 That makes `example(transmute(3u8), 0)` UB in *both* versions of `example`, and as such the optimization is correct for this input (and indeed for all possible inputs).
83
84 Now, one might argue that `b` in the example here was not truly "unused", it was just "used in dead code".
85 So instead of saying that `b` must be *always* valid, we could somehow try to make the use of `b` in dead code affect whether or not the program is UB.
86 However, that is a huge can of worms.
87 Right now, we have the fundamental principle that *dead code cannot affect program behavior*.
88 This principle is crucial for tools like [Miri](https://github.com/rust-lang/miri/): since Miri is an interpreter, it never even sees dead code.
89 I would argue that being able to have tools like Miri is hugely important, and it is worth having a semantics that enables such tools to exist.
90 But this means our hands are pretty much tied: since we cannot take into account of `b` is "used in dead code", we simply have to require `b` to always be valid, no matter whether and where it is "used" or not.
91 To support inlining and outlining, we also do not want the function boundary to be relevant, which ultimately leads us to the rule that Rust requires today: whenever data of a given type is *produced* anywhere, the data needs to be valid for that type.
92
93 I hope this post was helpful in explaining why Undefined Behavior in Rust is defined the way it is.
94 As usual, if you have any comments or questions, let me know in the [forums](https://internals.rust-lang.org/t/why-even-unused-data-needs-to-be-valid/12734).