经检查的未初始化的内存
和 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 可以 Copylet z2 = y; // 现在 y 逻辑上未初始化,因为 Box 不能 Copy}
然而,在这个例子中重新给y赋值需要将y标记为可变,这样一个安全的 Rust 程序就可以观察到y的值发生了变化:
fn main() {let mut y = Box::new(0);let z = y; // 现在 y 逻辑上未初始化,因为 Box 不能 Copyy = Box::new(1); // 重新初始化 y}
否则y就像是一个全新的变量。
