经检查的未初始化的内存
和 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 }
当然,虽然分析不考虑实际值,但它对依赖关系和控制流有相对复杂的理解。例如,这样是可以编译通过的:
#![allow(unused)] fn main() { 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
就像是一个全新的变量。