]>
Commit | Line | Data |
---|---|---|
8bb4bdeb | 1 | # Checked Uninitialized Memory |
c1a9b12d SL |
2 | |
3 | Like C, all stack variables in Rust are uninitialized until a value is | |
4 | explicitly assigned to them. Unlike C, Rust statically prevents you from ever | |
5 | reading them until you do: | |
6 | ||
48663c56 | 7 | ```rust,compile_fail |
c1a9b12d SL |
8 | fn main() { |
9 | let x: i32; | |
10 | println!("{}", x); | |
11 | } | |
12 | ``` | |
13 | ||
14 | ```text | |
0bf4aa26 XL |
15 | | |
16 | 3 | println!("{}", x); | |
17 | | ^ use of possibly uninitialized `x` | |
c1a9b12d SL |
18 | ``` |
19 | ||
20 | This is based off of a basic branch analysis: every branch must assign a value | |
04454e1e FG |
21 | to `x` before it is first used. For short, we also say that "`x` is init" or |
22 | "`x` is uninit". | |
23 | ||
24 | Interestingly, Rust doesn't require the variable | |
c1a9b12d SL |
25 | to be mutable to perform a delayed initialization if every branch assigns |
26 | exactly once. However the analysis does not take advantage of constant analysis | |
27 | or anything like that. So this compiles: | |
28 | ||
29 | ```rust | |
30 | fn main() { | |
31 | let x: i32; | |
32 | ||
33 | if true { | |
34 | x = 1; | |
35 | } else { | |
36 | x = 2; | |
37 | } | |
38 | ||
39 | println!("{}", x); | |
40 | } | |
41 | ``` | |
42 | ||
43 | but this doesn't: | |
44 | ||
48663c56 | 45 | ```rust,compile_fail |
c1a9b12d SL |
46 | fn main() { |
47 | let x: i32; | |
48 | if true { | |
49 | x = 1; | |
50 | } | |
51 | println!("{}", x); | |
52 | } | |
53 | ``` | |
54 | ||
55 | ```text | |
0bf4aa26 XL |
56 | | |
57 | 6 | println!("{}", x); | |
58 | | ^ use of possibly uninitialized `x` | |
c1a9b12d SL |
59 | ``` |
60 | ||
61 | while this does: | |
62 | ||
63 | ```rust | |
64 | fn main() { | |
65 | let x: i32; | |
66 | if true { | |
67 | x = 1; | |
68 | println!("{}", x); | |
69 | } | |
70 | // Don't care that there are branches where it's not initialized | |
71 | // since we don't use the value in those branches | |
72 | } | |
73 | ``` | |
74 | ||
75 | Of course, while the analysis doesn't consider actual values, it does | |
76 | have a relatively sophisticated understanding of dependencies and control | |
77 | flow. For instance, this works: | |
78 | ||
79 | ```rust | |
80 | let x: i32; | |
81 | ||
82 | loop { | |
83 | // Rust doesn't understand that this branch will be taken unconditionally, | |
84 | // because it relies on actual values. | |
85 | if true { | |
86 | // But it does understand that it will only be taken once because | |
87 | // we unconditionally break out of it. Therefore `x` doesn't | |
88 | // need to be marked as mutable. | |
89 | x = 0; | |
90 | break; | |
91 | } | |
92 | } | |
93 | // It also knows that it's impossible to get here without reaching the break. | |
94 | // And therefore that `x` must be initialized here! | |
95 | println!("{}", x); | |
96 | ``` | |
97 | ||
98 | If a value is moved out of a variable, that variable becomes logically | |
99 | uninitialized if the type of the value isn't Copy. That is: | |
100 | ||
101 | ```rust | |
102 | fn main() { | |
103 | let x = 0; | |
104 | let y = Box::new(0); | |
105 | let z1 = x; // x is still valid because i32 is Copy | |
106 | let z2 = y; // y is now logically uninitialized because Box isn't Copy | |
107 | } | |
108 | ``` | |
109 | ||
110 | However reassigning `y` in this example *would* require `y` to be marked as | |
111 | mutable, as a Safe Rust program could observe that the value of `y` changed: | |
112 | ||
113 | ```rust | |
114 | fn main() { | |
115 | let mut y = Box::new(0); | |
116 | let z = y; // y is now logically uninitialized because Box isn't Copy | |
117 | y = Box::new(1); // reinitialize y | |
118 | } | |
119 | ``` | |
120 | ||
121 | Otherwise it's like `y` is a brand new variable. |