检查未初始化的内存
像 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
}
当然,虽然分析不考虑实际值,但它对依赖关系和控制流有相对复杂的理解。例如,这段代码可以运行:
如果一个值从变量中被移动出去,那么如果该值的类型不是 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` 是一个全新的变量。