| # Checked Uninitialized Memory |
| |
| Like C, all stack variables in Rust are uninitialized until a value is |
| explicitly assigned to them. Unlike C, Rust statically prevents you from ever |
| reading them until you do: |
| |
| ```rust,compile_fail |
| fn main() { |
| let x: i32; |
| println!("{}", x); |
| } |
| ``` |
| |
| ```text |
| | |
| 3 | println!("{}", x); |
| | ^ use of possibly uninitialized `x` |
| ``` |
| |
| This is based off of a basic branch analysis: every branch must assign a value |
| to `x` before it is first used. For short, we also say that "`x` is init" or |
| "`x` is uninit". |
| |
| Interestingly, Rust doesn't require the variable |
| to be mutable to perform a delayed initialization if every branch assigns |
| exactly once. However the analysis does not take advantage of constant analysis |
| or anything like that. So this compiles: |
| |
| ```rust |
| fn main() { |
| let x: i32; |
| |
| if true { |
| x = 1; |
| } else { |
| x = 2; |
| } |
| |
| println!("{}", x); |
| } |
| ``` |
| |
| but this doesn't: |
| |
| ```rust,compile_fail |
| fn main() { |
| let x: i32; |
| if true { |
| x = 1; |
| } |
| println!("{}", x); |
| } |
| ``` |
| |
| ```text |
| | |
| 6 | println!("{}", x); |
| | ^ use of possibly uninitialized `x` |
| ``` |
| |
| while this does: |
| |
| ```rust |
| fn main() { |
| let x: i32; |
| if true { |
| x = 1; |
| println!("{}", x); |
| } |
| // Don't care that there are branches where it's not initialized |
| // since we don't use the value in those branches |
| } |
| ``` |
| |
| Of course, while the analysis doesn't consider actual values, it does |
| have a relatively sophisticated understanding of dependencies and control |
| flow. For instance, this works: |
| |
| ```rust |
| let x: i32; |
| |
| loop { |
| // Rust doesn't understand that this branch will be taken unconditionally, |
| // because it relies on actual values. |
| if true { |
| // But it does understand that it will only be taken once because |
| // we unconditionally break out of it. Therefore `x` doesn't |
| // need to be marked as mutable. |
| x = 0; |
| break; |
| } |
| } |
| // It also knows that it's impossible to get here without reaching the break. |
| // And therefore that `x` must be initialized here! |
| println!("{}", x); |
| ``` |
| |
| If a value is moved out of a variable, that variable becomes logically |
| uninitialized if the type of the value isn't Copy. That is: |
| |
| ```rust |
| fn main() { |
| let x = 0; |
| let y = Box::new(0); |
| let z1 = x; // x is still valid because i32 is Copy |
| let z2 = y; // y is now logically uninitialized because Box isn't Copy |
| } |
| ``` |
| |
| However reassigning `y` in this example *would* require `y` to be marked as |
| mutable, as a Safe Rust program could observe that the value of `y` changed: |
| |
| ```rust |
| fn main() { |
| let mut y = Box::new(0); |
| let z = y; // y is now logically uninitialized because Box isn't Copy |
| y = Box::new(1); // reinitialize y |
| } |
| ``` |
| |
| Otherwise it's like `y` is a brand new variable. |