龙空技术网

一起学Rust编程「6」:变量的声明

红豆云的红小豆 216

前言:

现在各位老铁们对“变量声明的规则”大体比较着重,你们都想要剖析一些“变量声明的规则”的相关内容。那么小编也在网上搜集了一些对于“变量声明的规则””的相关内容,希望各位老铁们能喜欢,大家快快来了解一下吧!

我们在第3节已经用let关键字定义了一些基本类型的变量,也在九九乘法表程序中用println!宏打印了变量的值。在很多的语言里,变量的定义和使用规则都是比较直观的,并不需要作特别多的解释。但是Rust不同,Rust语言的设计者希望通过一些精心设计的数据(变量)使用的规则来保证内存安全。

接下来我们详细的了解一下Rust与其它语言最迥异的区别——围绕变量而生的一些约定。本节重点讲解变量的声明,下一节我们再来看变量的引用和所有权。

变量的定义:let关键字

我们已经知道所有的变量都需要先用let定义后才能使用。没有定义的变量会引起编译错误:

fn main() {    let a = 10; // OK    b = 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`.

当然,肉眼很容易看出3 > 2是一个恒为真的常量表达式,所以a = 1是一定会执行的,也就是println! 从程序逻辑上来讲,并不会访问未初始化的变量。这里仍然不能编译通过是因为目前的rustc(1.45.2)在编译期还没有对这种常量表达式的推导。

未使用的变量

如果你勤恳地编译了前面的一些示例代码,你可能已经注意到了,有时编译器会产生类似这样的警告:

warning: variable `a` is assigned to, but never used --> var.rs:2:9  |2 |     let a;  |         ^  |  = note: `#[warn(unused_variables)]` on by default  = note: consider using `_a` instead

这是因为我们的变量在声明或者赋值以后,完全没有使用它的值。Rust希望通过这样的警告来避免无用代码,以保持程序整洁和可读。

有时出于语法需要,定义一些不被使用的变量是不可避免的。这些情况下,可以像警告里提示的那样,用一个简单的下划线_,或者一个以下划线_开头的变量名来告诉编译器“我知道这个变量的值确实不需要被用到,不用再警告我了”。

例如,这个写法可以用在for循环中:

fn main() {    for _ in 1..10 {        println!("hi there!");    }}

或者用在match的模式匹配中:

fn main() {    let a = (0, 1, 2);    let b = match a {        (0, _y, z) => z,        (1, y, _z) => y,        (x, _y, _z) => x,    };    println!("b is {}", b);}
变量可变性:mut的使用

到现在为止,我们对变量只做过声明和初始化。如果你试着在初始化以后对变量的值进行修改,可能会得到下面的编译错误:

error[E0384]: cannot assign twice to immutable variable `a` --> var.rs:3:5  |2 |     let a = 1;  |         -  |         |  |         first assignment to `a`  |         help: make this binding mutable: `mut a`3 |     a = 2;  |     ^^^^^ cannot assign twice to immutable variableerror: aborting due to previous error; 1 warning emittedFor more information about this error, try `rustc --explain E0384`.

这是因为Rust跟很多其他的语言相反,用let声明的变量默认是“只读”的,也就是一旦赋予了初始值,就不能再进行改变。

这个设计的意图很明显,就是鼓励开发者尽可能地使用只读变量。这样做的好处也显而易见:一方面可以避免错误地对一个不该被修改的变量赋值引入bug,另一方面在阅读代码时,一个只读的数据比一个可能会改变的数据更容易把握。

为了声明一个货真价实的变量,我们需要用到mut关键字。把let改成let mut即可:

fn main() {    let mut a = 1;    println!("a is {}", a);    a = 2;    println!("a is {}", a);}

变量可以看作是一个值(或者说一段数据)的“代号”。数据才是实实在在存在于计算机里的,CPU做运算真正访问的东西。而是否有mut,并不改变变量的这一本质属性,它只决定了在语法层面,能否通过这一变量来修改其背后的数据。

这里需要注意,不可修改的变量跟我们通常理解的常量是不一样的。Rust里的常量是一个“字面上的值”,需要用const关键字来定义,并且必须指定类型:

const PI :f32 = 3.1416;fn main() {    let r = 3f32;    println!("Perimeter of a circle with radius of {} is {}",             r, r * 2.0 * PI);}

换句话说,常量也可以看作一种代码字面(lexical)的“替换”,上面的代码可以等价于:

fn main() {    let r = 3f32;    println!("Perimeter of a circle with radius of {} is {}",             r, r * 2.0 * 3.1416f32);}

最后我们照例用一个例子来结束今天的学习。

fn gcd(a :u32, b: u32) -> u32 {    let max = |u, v| if u > v { u } else { v };    let min = |u, v| if u < v { u } else { v };    let mut x = max(a, b);    let mut y = min(a, b);    while x % y != 0 {        let xx = x;        x = y;        y = xx % y    }    y}fn main() {    let x = 320;    let y = 88;    println!("greatest common divisor of {} and {} is {}",             x, y, gcd(x, y));}

关注红小豆,一起学习Rust开发。欢迎点赞,转发,收藏!

标签: #变量声明的规则