经检查的未初始化的内存
和 C 语言一样,Rust 中的所有堆栈变量都是未初始化的,直到为它们明确赋值。与 C 不同的是,Rust 静态地阻止你读取它们,直到你为它们赋值。
fn main() {
let x: i32;
println!("{}", x);
}
|
3 | println!("{}", x);
| ^ 使用了没有初始化的 `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);
| ^ 使用了可能没有初始化的 `x`
这个又可以了:
fn main() {
let x: i32;
if true {
x = 1;
println!("{}", x);
}
// 不需要担心还有没有初始化 x 的分支,
// 因为我们实际上并没有在别的分支使用 x
}
当然,虽然分析不考虑实际值,但它对依赖关系和控制流有相对复杂的理解。例如,这样是可以编译通过的:
let x: i32;
loop {
// Rust 不知道这个分支会被无条件执行,
// 因为它依赖于实际值
if true {
// 但是它确实知道循环只会有一次,
// 因为我们会无条件 break,
// 所以 x 不需要是可变的
x = 0;
break;
}
}
// Rust 知道如果没有执行 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
就像是一个全新的变量。