已检查的未初始化内存

与 C 语言类似,Rust 中的所有栈变量在被显式赋值之前都是未初始化的。与 C 语言不同的是,Rust 会静态地阻止你在赋值之前读取它们。

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

这是基于基本的代码分支分析:每个分支都必须在第一次使用 x 之前为其赋值。简而言之,我们也说“x 已初始化”或“x 未初始化”。

有趣的是,如果每个分支都只赋值一次,Rust 不要求变量是可变的来执行延迟初始化。但是,分析不会利用常量分析或类似的东西。所以这段代码可以编译:

fn main() {
    let x: i32;

    if true {
        x = 1;
    } else {
        x = 2;
    }

    println!("{}", x);
}

但这段代码不行:

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

而这段代码可以:

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
}

当然,虽然分析不考虑实际值,但它对依赖关系和控制流有相对复杂的理解。例如,这段代码可以工作:

#![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);
}

如果一个值从一个变量中移出,并且该值的类型不是 Copy,则该变量在逻辑上变为未初始化。也就是说:

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
}

但是,在这个例子中重新赋值 y 将要求 y 被标记为可变的,因为安全的 Rust 程序可以观察到 y 的值发生了变化。

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
}

否则,它就像 y 是一个全新的变量。