吾本轻狂|一起学Rust编程「6」:变量的声明

我们在第3节已经用let关键字定义了一些基本类型的变量 , 也在九九乘法表程序中用println!宏打印了变量的值 。 在很多的语言里 , 变量的定义和使用规则都是比较直观的 , 并不需要作特别多的解释 。 但是Rust不同 , Rust语言的设计者希望通过一些精心设计的数据(变量)使用的规则来保证内存安全 。
接下来我们详细的了解一下Rust与其它语言最迥异的区别——围绕变量而生的一些约定 。 本节重点讲解变量的声明 , 下一节我们再来看变量的引用和所有权 。
吾本轻狂|一起学Rust编程「6」:变量的声明变量的定义:let关键字【吾本轻狂|一起学Rust编程「6」:变量的声明】我们已经知道所有的变量都需要先用let定义后才能使用 。 没有定义的变量会引起编译错误:
fn main() {let a = 10; // OKb = 20;// Error}尝试编译上面的代码会得到这样的错误:
error[E0425]: cannot find value `b` in this scope --> var.rs:3:5|3 |b = 20;|^ help: a local variable with a similar name exists: `a`error: aborting due to previous errorFor more information about this error, try `rustc --explain E0425`.在b = 20这一行加上let , 就变成了合法的代码 。
Rust还允许我们故意的“覆盖”一个变量 。 也就是通过重新let已有的变量名 , 让后续代码使用一个全新的变量 , 其类型甚至也可以跟之前不同 。 也就是说像这样的写法是可以通过的:
fn main() {let a = 10;println!("a is {}", a);let a = "hello";println!("a is {}", a);}上面的代码会有2行输出 。 上半部分的a是一个整数 , 下半部分被覆盖为一个字符串 。
自然地 , 子代码块(例如条件/循环体)里的let也可以覆盖父作用域里存在的同名变量:
fn main() {let a = 10;println!("a is {}", a);for i in 1..10 {let a = "hello";println!("a is {}", a);}}let的一般形式都是在变量名后面加= , 在声明的同时提供一个初始值 。 但是Rust也允许let只声明变量 , 而省略初始值 。 这样的变量 , 在使用前还是需要赋值 , 否则会出现编译错误 。 例如:
fn main() {let a;if 3 > 2 {a = "sane";} else {a = "insane";}println!("The world is {}.", a);}上面的例子 , 实际上省掉了一个不必要且略显生硬的默认值 。 换句话说 , 假设我们写作let a = ""或者let a = "TBD" , 代码读起来都会有额外的理解负担 。 一则 , 初始值很快就被覆盖 , 所以并没有起到什么作用;再者 , 不论默认值是什么 , 都有点意图不明 。
技术上来说 , 给所有的let都加一个有意义的初始值是可以做到的 。 前面提到过 , if可以是一个有值的表达式 , 所以把整个逻辑糅合成let a = if ...这样的语法也是可以的 。 甚至 , 我们还可以使用代码块{}来包含更复杂的程序来计算初始值 。 但是 , 把声明放在单独的一行 , 有时候会使得程序结构更加清晰和简洁 。
在有些语言里 , 这样做有可能造成使用未初始化的变量的bug , 但是Rust的编译检查已经对这种情况做了判断 , 从而杜绝了忘记初始化变量的可能 。
比如下面的代码尝试打印一个在编译器看来“可能”没有被初始化的变量:
fn main() {let a;if 3 > 2 {a = 1;}println!("a is {}", a);}编译器会报出这样的错误:
error[E0381]: borrow of possibly-uninitialized variable: `a` --> var.rs:8:25|8 |println!("a is {}", a);|^ use of possibly-uninitialized `a`error: aborting due to previous errorFor more information about this error, try `rustc --explain E0381`.


推荐阅读