安全方式

和C一样,所有栈上的变量在显式赋值之前都是未初始化的。而和C不同的是,Rust禁止你在赋值之前读取它们:

fn main() {
    let x: i32;
    println!("{}", x);
}
src/main.rs:3:20: 3:21 error: use of possibly uninitialized variable: `x`
src/main.rs:3     println!("{}", 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);
}
src/main.rs:6:17: 6:18 error: use of possibly uninitialized variable: `x`
src/main.rs:6   println!("{}", x);

而这一段又可以编译:

fn main() {
    let x: i32;
    if true {
        x = 1;
        println!("{}", x);
    }
    // 不关心其他的未初始化变量的分支
    // 因为我们并不使用那些分支
}

当然,虽然分析过程不知道变量的实际值,它对依赖和控制流程的理解还是比较深入的。比如,这段代码是正确的:

let x: i32;

loop {
    // Rust不知道这个分支会被无条件执行
    //因为它依赖于实际值
    if true {
        // 但是它确实知道循环只会有一次,因为我们会无条件break
        // 所以x不需要是可变的
        x = 0;
        break;
    }
}
// 它也知道如果没有执行break的话,代码不会运行到这里
// 所以在这里x一定已经被初始化了
println!("{}", x);

如果值从变量中移出且变量类型不是Copy,那么变量逻辑上处于未初始化状态。就是说:

fn main() {
    let x = 0;
    let y = Box::new(0);
    let z1 = x; // x仍然是合法的,因为i32是Copy
    let z2 = y; // y现在逻辑上未初始化,因为Box不是Copy
}

但是,这个例子中对y重新赋值要求y是可变的,因为安全Rust能够观察到y的值发生了变化:

fn main() {
    let mut y = Box::new(0);
    let z = y; // y现在逻辑上未初始化,因为Box不是Copy
    y = Box::new(1); // 重新初始化y
}

否则y会被视为一个全新的变量。

Drop标志:前一章的例子涉及到Rust的一个有趣的问题。我们看到我们可以安全地为一段内存初始化、反初始化、再初始化。对于Copy类型,这一点不是很重要,因为数据不过是一堆字节而已。但是对于有析构函数的类型就是另外一回事了:变量每次被赋值或 ...