Rust 教程 -01- 变量绑定与解构

变量命名

在命名方面,和其他语言没有区别,不过当给变量命名时,需遵循 Rust 命名规范。

Rust 语言有一些关键字keywords),和其他语言一样,这些关键字都是被保留给 Rust 语言使用的,因此,它们不能被用作变量或函数的名称。在 Rust 关键字 中可找到关键字列表。

变量绑定

在其它语言中,我们用 var a = "hello world" 的方式给 a 赋值,也就是把等式右边的 "hello world" 字符串赋值给变量 a,而在 rust 中,我们这样写:let a = "hello world",同时给这个过程起了另一个名字:变量绑定

为何不用赋值而用绑定呢(其实你也可以称之为赋值,但是绑定的含义更清晰准确)?这里就涉及 Rust 最核心的原则——所有权,简单来讲,任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人(聪明的读者应该能猜到,在这种情况下,该对象之前的主人就会丧失对该对象的所有权),像极了我们的现实世界,不是吗?

那为什么要引进“所有权”这个新的概念呢?请稍安勿躁,时机一旦成熟,我们就回来继续讨论这个话题。

变量可变性

Rust 的变量在默认情况下是不可变的。当然你可以通过 mut 关键字让变量变为可变的,让设计更灵活。

如果变量 a 不可变,那么一旦为它绑定值,就不能再修改 a。举个例子,在我们的工程目录下使用 cargo new variables 新建一个项目,叫做 variables。

然后在新建的 variables 目录下,编辑 src/main.rs,改为下面代码:

fn main() { let x = 5; println!("The value of x is: {}", x); x = 6; println!("The value of x is: {}", x); }

保存文件,再使用 cargo run 运行它,迎面而来的是一条错误提示:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) error[E0384]: cannot assign twice to immutable variable `x` --> src/main.rs:4:5 | 2 | let x = 5; | - | | | first assignment to `x` | help: consider making this binding mutable: `mut x` 3 | println!("The value of x is: {}", x); 4 | x = 6; | ^^^^^ cannot assign twice to immutable variable error: aborting due to previous error

具体的错误原因为 cannot assign twice to immutable variable x(无法对不可变的变量重复赋值),因为我们想为不可变的 x 变量再次赋值。

在 Rust 中,可变性很简单,只要在变量名前加一个 mut 即可,而且这种显示声明方式还会给后来人传达这样的信息:嗯,这个变量在后面代码部分会发生改变。

为了让变量为可变,将 src/main.rs 改为以下内容:

fn main() { let mut x = 5; println!("The value of x is: {}", x); x = 6; println!("The value of x is: {}", x); }

运行程序将得到下面结果:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) Finished dev [unoptimized + debuginfo] target(s) in 0.30s Running `target/debug/variables` The value of x is: 5 The value of x is: 6

选择可变还是不可变,更多的还是取决于你的使用场景,例如不可变可以带来安全性,但是丧失了灵活性和性能(如果你要改变,就要重新创建一个新的变量,这里涉及到内存对象的再分配)。而可变变量最大的好处就是使用上的灵活性和性能上的提升。

例如,在使用大型数据结构或者热点代码路径(被大量频繁调用)的情形下,在同一内存位置更新实例可能比复制并返回新分配的实例要更快。使用较小的数据结构时,通常创建新的实例并以更具函数式的风格来编写程序,可能会更容易理解,所以值得以较低的性能开销来确保代码清晰。

使用下划线开头忽略未使用的变量

如果你创建了一个变量却不在任何地方中使用它,Rust 通常会给你一个警告,因为这可能会是个 Bug。但是有时创建一个不会被使用的变量是有用的,比如正在设计原型或刚刚开始一个项目。这时你希望告诉 Rust 不要警告未使用的变量,为此可以用下划线作为变量名的开头

fn main() { let _x = 5; let y = 10; }

使用 cargo run 运行下试试:

warning: unused variable: y
--> src/main.rs:3:9
|
3 | let y = 10;
| ^ help: 如果 y 故意不被使用,请添加一个下划线前缀: _y
|
= note: #[warn(unused_variables)] on by default

可以看到,两个变量都只是声明,没有使用,但编译器却只给出了 y 未被使用的警告,充分说明了 _ 变量名前缀在这里发挥的作用。

变量解构

let 表达式不仅仅用于变量的绑定,还能进行复杂变量的解构:从一个相对复杂的变量中,匹配出该变量的一部分内容:

fn main() { let (a, mut b) = (true, false); // a = true,不可变; b = false,可变 println!("a = {:?}, b = {:?}", a, b); b = true; assert_eq!(a, b); }

解构式赋值

在 Rust 1.59 版本后,可以在赋值语句中的左式中使用元祖、切片和结构体模式了。

#[allow(dead_code)] struct Struct { e: i32, f: i32, } fn main() { let (a, b, c, d, e): (i32, i32, i32, i32, i32); (a, b) = (1, 2); // _ 代表匹配一个值,但是我们不关心具体的值是什么,因为没有使用一个变量名而是 _ [c, .., d, _] = [1, 2, 3, 4, 5]; Struct { e, .. } = Struct { e: 5, f: 6 }; assert_eq!([1, 2, 1, 4, 5], [a, b, c, d, e]) }

这种使用方式跟之前的 let 保持了一致性,但是 let 会重新绑定,而这里仅仅对之前绑定的变量进行再赋值。

需要注意的是,使用 += 的赋值语句还不支持解构式赋值。

变量和常量之间的差异

常量与不可变变量一样,常量也是绑定到一个常量名且不允许更改的值,但是常量和变量之间存在一些差异:

  • 常量不允许使用 mut常量不仅默认不可变,而且自始至终不可变。因为常量在编译完成后,就已经确定了它的值。
  • 常量使用 const 关键字而不是 let 关键字来声明,并且值的类型必须标注。

下面是一个常量声明的例子,其常量名为 MAX_POINTS,值为 100,000。(Rust 常量的命名约定是全部字母都大写,并使用下划线分隔单词,另外对数字字面量可插入下划线以提高可读性)

const MAX_POINTS: u32 = 100_000;

常量可以在任意作用域内声明,包括全局作用域,在声明的作用域内,常量在程序运行的整个过程中都有效。对于需要在多处代码共享一个不可变的值时非常有用,例如游戏中允许玩家赚取的最大点数或光速。

变量遮蔽

Rust 允许声明相同的变量名,在后面声明的变量会遮蔽掉前面声明的,如下所示:

fn main() { let x = 5; // 在main函数的作用域内对之前的x进行遮蔽 let x = x + 1; { // 在当前的花括号作用域内,对之前的x进行遮蔽 let x = x * 2; println!("The value of x in the inner scope is: {}", x); } println!("The value of x is: {}", x); }

这个程序首先将数值 5 绑定到 x,然后通过重复使用 let x = 来遮蔽之前的 x,并取原来的值加上 1,所以 x 的值变成了 6。第三个 let 语句同样遮蔽前面的 x,取之前的值并乘上 2,得到的 x 最终值为 12。当运行此程序,将输出以下内容:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) ... The value of x in the inner scope is: 12 The value of x is: 6

这和 mut 变量的使用是不同的,第二个 let 生成了完全不同的新变量,两个变量只是恰好拥有同样的名称,涉及一次内存对象的再分配 ,而 mut 声明的变量,可以修改同一个内存地址上的值,并不会发生内存对象的再分配,性能要更好。

变量遮蔽的用处在于,如果你在某个作用域内无需再使用之前的变量(在被遮蔽后,无法再访问到之前的同名变量),就可以重复的使用变量名字,而不用绞尽脑汁去想更多的名字。

例如,假设有一个程序要统计一个空格字符串的空格数量:

// 字符串类型 let spaces = " "; // usize 数值类型 let spaces = spaces.len();

这种结构是允许的,因为第一个 spaces 变量是一个字符串类型,第二个 spaces 变量是一个全新的变量且和第一个具有相同的变量名,且是一个数值类型。所以变量遮蔽可以帮我们节省些脑细胞,不用去想如 spaces_strspaces_num 此类的变量名;相反我们可以重复使用更简单的 spaces 变量名。如果你不用 let :

let mut spaces = " "; spaces = spaces.len();

运行一下,你就会发现编译器报错:

$ cargo run Compiling variables v0.1.0 (file:///projects/variables) error[E0308]: mismatched types --> src/main.rs:3:14 | 3 | spaces = spaces.len(); | ^^^^^^^^^^^^ expected `&str`, found `usize` error: aborting due to previous error

显然,Rust 对类型的要求很严格,不允许将整数类型 usize 赋值给 字符串类型usize 是一种 CPU 相关的整数类型。

  • Rust

    Rust 是一门赋予每个人构建可靠且高效软件能力的语言。Rust 由 Mozilla 开发,最早发布于 2014 年 9 月。

    59 引用 • 22 回帖 • 6 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...