原文链接:https://doc.rust-lang.org/nomicon/checked-uninit.html

安全方式

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

  1. fn main() {
  2. let x: i32;
  3. println!("{}", x);
  4. }
  1. src/main.rs:3:20: 3:21 error: use of possibly uninitialized variable: `x`
  2. src/main.rs:3 println!("{}", x);
  3. ^

这个错误基于分支分析:任何一个分支在第一次使用x之前都必须对它赋值。有意思的是,如果每一个分支都只赋值一次的话,Rust并不要求变量是可变的。但是,这个分析过程没有配合常量分析。所以下面这段代码可以编译:

  1. fn main() {
  2. let x: i32;
  3. if true {
  4. x = 1;
  5. } else {
  6. x = 2;
  7. }
  8. println!("{}", x);
  9. }

但是这段却不能编译:

  1. fn main() {
  2. let x: i32;
  3. if true {
  4. x = 1;
  5. }
  6. println!("{}", x);
  7. }
  1. src/main.rs:6:17: 6:18 error: use of possibly uninitialized variable: `x`
  2. src/main.rs:6 println!("{}", x);

而这一段又可以编译:

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

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

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

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

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

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

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

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