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:
fn main() { let x: i32; println!("{}", x); }
|
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:
fn main() { let x: i32; if true { x = 1; } else { x = 2; } println!("{}", x); }
but this doesn't:
fn main() { let x: i32; if true { x = 1; } println!("{}", x); }
|
6 | println!("{}", x);
| ^ use of possibly uninitialized `x`
while this does:
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:
#![allow(unused)] fn main() { 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:
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:
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.