安全方式
和C一样,所有栈上的变量在显式赋值之前都是未初始化的。而和C不同的是,Rust禁止你在赋值之前读取它们:
fn main() { let x: i32; println!("{}", x); }
src/main.rs:3:20: 3:21 error: use of possibly uninitialized variable: `x` src/main.rs:3 println!("{}", 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); }
src/main.rs:6:17: 6:18 error: use of possibly uninitialized variable: `x` src/main.rs:6 println!("{}", x);
而这一段又可以编译:
fn main() { let x: i32; if true { x = 1; println!("{}", x); } // 不关心其他的未初始化变量的分支 // 因为我们并不使用那些分支 }
当然,虽然分析过程不知道变量的实际值,它对依赖和控制流程的理解还是比较深入的。比如,这段代码是正确的:
let x: i32; loop { // Rust不知道这个分支会被无条件执行 //因为它依赖于实际值 if true { // 但是它确实知道循环只会有一次,因为我们会无条件break // 所以x不需要是可变的 x = 0; break; } } // 它也知道如果没有执行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
会被视为一个全新的变量。
Drop标志:前一章的例子涉及到Rust的一个有趣的问题。我们看到我们可以安全地为一段内存初始化、反初始化、再初始化。对于Copy类型,这一点不是很重要,因为数据不过是一堆字节而已。但是对于有析构函数的类型就是另外一回事了:变量每次被赋值或 ...