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.