Checked Uninitialized Memory

Like C, all stack variables in Rust are uninitialized until a value isexplicitly assigned to them. Unlike C, Rust statically prevents you from everreading them until you do:

  1. fn main() {
  2. let x: i32;
  3. println!("{}", x);
  4. }
  1. |
  2. 3 | println!("{}", x);
  3. | ^ use of possibly uninitialized `x`

This is based off of a basic branch analysis: every branch must assign a valueto x before it is first used. Interestingly, Rust doesn’t require the variableto be mutable to perform a delayed initialization if every branch assignsexactly once. However the analysis does not take advantage of constant analysisor anything like that. So this compiles:

  1. fn main() {
  2. let x: i32;
  3. if true {
  4. x = 1;
  5. } else {
  6. x = 2;
  7. }
  8. println!("{}", x);
  9. }

but this doesn’t:

  1. fn main() {
  2. let x: i32;
  3. if true {
  4. x = 1;
  5. }
  6. println!("{}", x);
  7. }
  1. |
  2. 6 | println!("{}", x);
  3. | ^ use of possibly uninitialized `x`

while this does:

  1. fn main() {
  2. let x: i32;
  3. if true {
  4. x = 1;
  5. println!("{}", x);
  6. }
  7. // Don't care that there are branches where it's not initialized
  8. // since we don't use the value in those branches
  9. }

Of course, while the analysis doesn’t consider actual values, it doeshave a relatively sophisticated understanding of dependencies and controlflow. For instance, this works:

  1. let x: i32;
  2. loop {
  3. // Rust doesn't understand that this branch will be taken unconditionally,
  4. // because it relies on actual values.
  5. if true {
  6. // But it does understand that it will only be taken once because
  7. // we unconditionally break out of it. Therefore `x` doesn't
  8. // need to be marked as mutable.
  9. x = 0;
  10. break;
  11. }
  12. }
  13. // It also knows that it's impossible to get here without reaching the break.
  14. // And therefore that `x` must be initialized here!
  15. println!("{}", x);

If a value is moved out of a variable, that variable becomes logicallyuninitialized if the type of the value isn’t Copy. That is:

  1. fn main() {
  2. let x = 0;
  3. let y = Box::new(0);
  4. let z1 = x; // x is still valid because i32 is Copy
  5. let z2 = y; // y is now logically uninitialized because Box isn't Copy
  6. }

However reassigning y in this example would require y to be marked asmutable, as a Safe Rust program could observe that the value of y changed:

  1. fn main() {
  2. let mut y = Box::new(0);
  3. let z = y; // y is now logically uninitialized because Box isn't Copy
  4. y = Box::new(1); // reinitialize y
  5. }

Otherwise it’s like y is a brand new variable.