检查未初始化的内存 (Checked Uninitialized Memory)

与C一样,Rust中的所有栈变量都是未初始化的,直到为它们显式赋值.与C不同,Rust会在你执行以下操作之前静态地阻止你读取它们:

  1. fn main() {
  2. let x: i32;
  3. println!("{}", x);
  4. }
  1. |
  2. 3 | println!("{}", x);
  3. | ^ use of possibly uninitialized `x`

这是基于一个基本的分支分析:每个分支必须在第一次使用之前为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. |
  2. 6 | println!("{}", x);
  3. | ^ use of possibly uninitialized `x`

而这样可以:

  1. fn main() {
  2. let x: i32;
  3. if true {
  4. x = 1;
  5. println!("{}", x);
  6. }
  7. // Don't care that there are branches where it's not initialized
  8. // since we don't use the value in those branches
  9. }

当然,虽然分析没有考虑实际值,但它确实对依赖关系和控制流有相对复杂的理解.例如,这有效:

  1. let x: i32;
  2. loop {
  3. // Rust doesn't understand that this branch will be taken unconditionally,
  4. // because it relies on actual values.
  5. if true {
  6. // But it does understand that it will only be taken once because
  7. // we unconditionally break out of it. Therefore `x` doesn't
  8. // need to be marked as mutable.
  9. x = 0;
  10. break;
  11. }
  12. }
  13. // It also knows that it's impossible to get here without reaching the break.
  14. // And therefore that `x` must be initialized here!
  15. println!("{}", x);

如果某个值被移出变量,那么如果值的类型不是Copy,则该变量在逻辑上将变为未初始化.那是:

  1. fn main() {
  2. let x = 0;
  3. let y = Box::new(0);
  4. let z1 = x; // x is still valid because i32 is Copy
  5. let z2 = y; // y is now logically uninitialized because Box isn't Copy
  6. }

但是,在此示例中重新赋值y将需要 将(would) y标记为可变,因为Safe Rust程序可以观察到y的值已更改:

  1. fn main() {
  2. let mut y = Box::new(0);
  3. let z = y; // y is now logically uninitialized because Box isn't Copy
  4. y = Box::new(1); // reinitialize y
  5. }

否则就好像y是一个全新的变量.