文章目录
- [1. 前言](#1. 前言)
- [2. 背景](#2. 背景)
- [3. Rust 中的一般性编程概念](#3. Rust 中的一般性编程概念)
-
- [3.1 变量及其可变性(Mutability)](#3.1 变量及其可变性(Mutability))
- [3.1.1 变量定义](#3.1.1 变量定义)
-
- [3.1.2 常量](#3.1.2 常量)
- [3.1.3 变量隐藏(Shadowing)](#3.1.3 变量隐藏(Shadowing))
- [3.2 基本类型](#3.2 基本类型)
-
- [3.2.1 标量(scalar)类型](#3.2.1 标量(scalar)类型)
-
- [3.2.1.1 整型(Integer Types)](#3.2.1.1 整型(Integer Types))
- [3.2.1.2 浮点型(Floating-Point Types)](#3.2.1.2 浮点型(Floating-Point Types))
- [3.2.1.3 数值运算(Numeric Operations)](#3.2.1.3 数值运算(Numeric Operations))
- [3.2.1.4 布尔类型(The Boolean Type)](#3.2.1.4 布尔类型(The Boolean Type))
- [3.2.1.5 字符类型(The Character Type)](#3.2.1.5 字符类型(The Character Type))
- [3.2.1.6 复合类型(Compound Types)](#3.2.1.6 复合类型(Compound Types))
-
- [3.2.1.6.1 元组(The Tuple Type)](#3.2.1.6.1 元组(The Tuple Type))
- [3.2.1.6.2 数组(The Array Type)](#3.2.1.6.2 数组(The Array Type))
-
- [3.2.1.6.2.1 访问数组元素](#3.2.1.6.2.1 访问数组元素)
- [3.2.1.6.2.2 非法访问数组元素](#3.2.1.6.2.2 非法访问数组元素)
- [3.3 函数](#3.3 函数)
-
- [3.3.1 函数参数](#3.3.1 函数参数)
- [3.3.2 语句 和 表达式](#3.3.2 语句 和 表达式)
- [3.3.3 函数返回值](#3.3.3 函数返回值)
- [3.4 注释](#3.4 注释)
- [3.5 控制流](#3.5 控制流)
-
- [3.5.1 if 表达式](#3.5.1 if 表达式)
-
- [3.5.1.1 在 let 语句中使用 if](#3.5.1.1 在 let 语句中使用 if)
- [3.5.2 循环](#3.5.2 循环)
-
- [3.5.2.1 `loop` 循环](#3.5.2.1
loop循环) -
- [3.5.2.1.1 从 `loop` 循环返回值](#3.5.2.1.1 从
loop循环返回值) - [3.5.2.1.2 `loop 标签`以消除多个 `loop` 之间的歧义](#3.5.2.1.2
loop 标签以消除多个loop之间的歧义)
- [3.5.2.1.1 从 `loop` 循环返回值](#3.5.2.1.1 从
- [3.5.2.2 `while` 循环](#3.5.2.2
while循环) - [3.5.2.3 `for` 循环](#3.5.2.3
for循环)
- [3.5.2.1 `loop` 循环](#3.5.2.1
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 背景
本文基于 Rust 文档 Common Programming Concepts 翻译整理而成。
3. Rust 中的一般性编程概念
本章节描述几乎所有编程语言都会出现的一般性编程概念,包括:
- 变量
- 基本类型
- 函数
- 注释
- 控制流
3.1 变量及其可变性(Mutability)
3.1.1 变量定义
Rust 通过 let 关键字来定义变量。如:
rust
let var = 5;
Rust 中定义的变量,默认是不可变的(immutable),下面的代码中,语句 x = 6; 试图修改变量 x,将会导致编译错误:
rust
fn main() {
let x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
bash
Compiling playground v0.0.1 (/playground)
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
想要将变量定义为可变的(mutable),可以在定义变量时加上 mut 修饰。前面的代码按如下修改,将不再产生编译错误:
rust
fn main() {
let mut x = 5;
println!("The value of x is: {x}");
x = 6;
println!("The value of x is: {x}");
}
3.1.2 常量
rust
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
定义常量,需要显式地指定类型。指定常量类型的方式是:在变量名后紧跟 : ,再加上类型。指定常量类型的方式也适用于变量。
3.1.3 变量隐藏(Shadowing)
bash
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {x}");
}
println!("The value of x is: {x}");
}
上面这段代码的运行输出如下:
bash
The value of x in the inner scope is: 12
The value of x is: 6
这里有两个关键点:
bash
1. Rust 允许定义同名变量,顺序上定义在后面变量隐藏前面的变量;
2. 变量有其作用域。
这和其它语言相似,上面里层 {} 内 x 的在离开 } 时就不再有效了。
Rust 甚至允许改变同名变量的类型:
rust
let spaces = " ";
let spaces = spaces.len();
这一点和 Python 类似。但下面的代码时不允许的,将会产生编译错误,这又和 Python 不同:
rust
let mut spaces = " ";
spaces = spaces.len();
3.2 基本类型
Rust 中任何变量都属于一种类型,本文关注标量(scalar) 和复合数据(compound)类型。 Rust 是一种静态类型语言,也就是说,在编译时编译器必须知道变量的类型,这和 C 语言 一样;和 C 语言不一样的是,Rust 编译器可以根据变量的使用方式,来推断变量的数据类型,但当一个变量在上下文中可以是多种类型时,这时候需要显式指定变量的类型,如:
rust
let guess: u32 = "42".parse().expect("Not a number!");
上面的代码将变量显式地指定为 u32 类型。如果不这样做,像如下编码:
c
let guess = "42".parse().expect("Not a number!");
编译器将爆出错误信息:
bash
error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `guess` an explicit type
|
2 | let guess: /* Type */ = "42".parse().expect("Not a number!");
| ++++++++++++
For more information about this error, try `rustc --explain E0284`.
error: could not compile `playground` (bin "playground") due to 1 previous error
错误信息 type annotations needed 明确的昭示,需要显式指定数据类型。
3.2.1 标量(scalar)类型
标量类型表示单个值。Rust 有四种主要的标量类型:整数、浮点数、布尔值、字符。让我们来看看它们在 Rust 中是如何工作的。
3.2.1.1 整型(Integer Types)
Rust 中的整型(Integer Types)有如下几种:
| Length | Signed | Unsigned |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| 128-bit | i128 | u128 |
| arch | isize | usize |
除了 arch 外,其它类型一目了然,没啥好说的。arch 类型数据位数取决于硬件架构:在 32-bit 架构下是 32-bit,在 64-bit 架构下是 64-bit 。
Rust 中数字常量的书写形式多种多样,如下表所示:
| Number literals | Example | 说明 |
|---|---|---|
| Decimal | 98_222 | 用 _ 作为分隔符 |
| Hex | 0xff | 十六进制形式 |
| Octal | 0o77 | 八进制形式 |
| Binary | 0b1111_0000 | 二进制形式 |
| Byte (u8 only) | b'A' | 字节,仅适用于 u8 类型 |
3.2.1.2 浮点型(Floating-Point Types)
Rust 的浮点型有 f32 和 f64 两种,默认位 f64。所有浮点都是有符号(signed)类型。看个例子:
rust
fn main() {
let x = 2.0; // f64
let y: f32 = 3.0; // f32
}
Rust 浮点根据 IEEE 754 标准表示,f32 为单精度(single-precision)浮点,f64 为双精度(double precision)浮点。
3.2.1.3 数值运算(Numeric Operations)
Rust 支持你期望的所有数字类型的基本数学运算:加法、减法、乘法、除法、余数。整数除法将向零截断到最接近的整数。以下代码显示了如何在 let 语句中使用每个数值运算:
rust
fn main() {
// addition
let sum = 5 + 10;
// subtraction
let difference = 95.5 - 4.3;
// multiplication
let product = 4 * 30;
// division
let quotient = 56.7 / 32.2;
let truncated = -5 / 3; // Results in -1
// remainder
let remainder = 43 % 5;
}
这些语句中的每个表达式都使用数学运算符,并计算为单个值,然后将其绑定到变量。
3.2.1.4 布尔类型(The Boolean Type)
与大多数其他编程语言一样,Rust 中的布尔类型有两个可能的值:true 和 false。布尔值的大小为 1 个字节。Rust 中的 布尔类型是使用 bool 指定的。例如:
rust
fn main() {
let t = true;
let f: bool = false; // with explicit type annotation
}
3.2.1.5 字符类型(The Character Type)
Rust 的 char 类型是该语言最原始的字母类型。以下是声明 char 值的一些示例:
rust
fn main() {
let c = 'z';
let z: char = 'ℤ'; // with explicit type annotation
let heart_eyed_cat = '😻';
}
请注意,我们使用单引号指定 char 文本,而不是使用双引号的字符串文本。Rust 的 char 类型大小为 4 个字节,表示一个 Unicode 标量值,这意味着它可以表示的不仅仅是 ASCII。重音字母、中文、日文和韩文字符、表情符号、和零宽度的空格都是 Rust 中的有效 char 值。Unicode 标量值的范围从 U+0000 到 U+D7FF 和 U+E000 到 U+10FFFF(含)。
3.2.1.6 复合类型(Compound Types)
复合类型可以将一组的多个值作为一种类型。Rust 有两种原始复合类型:元组(Tuple)和数组(Array)。
3.2.1.6.1 元组(The Tuple Type)
元组(Tuple)是将具有多种类型的多个值分组为一种复合类型的通用方法。元组具有固定长度:一旦声明,它们的大小就不能增大或缩小。
我们通过在括号内编写以逗号分隔的值列表来创建元组。元组中的每个位置都有一个类型,元组中不同值的类型不必相同。在此示例中,我们添加了可选的类型标注:
rust
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
变量 tup 绑定到整个元组,因为元组被视为单个复合元素。要从元组中获取单个值,我们可以使用模式匹配来解构元组值,如下所示:
rust
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {y}");
}
该程序首先创建一个元组并将其绑定到变量 tup。然后,它使用带有 let 的模式来获取 tup 并将其转换为三个单独的变量 x、y 和 z。这称为解构,因为它将单个元组分成三个部分。最后,程序打印 y 的值,即 6.4。
我们还可以通过使用句点 . 后跟我们要访问的值的索引来直接访问元组元素。例如:
rust
fn main() {
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
let six_point_four = x.1;
let one = x.2;
}
该程序创建元组 x 并使用各自的索引访问元组的每个元素。与大多数编程语言一样,元组中的第一个索引是 0。
没有任何值的元组具有特殊名称 unit。此值及其相应的类型都写入 () 并表示空值或空返回类型。如果表达式不返回任何其他值,则隐式返回 unit 值。
3.2.1.6.2 数组(The Array Type)
拥有多个值的集合的另一种方法是使用数组(array)。与元组(tuple)不同,数组的每个元素都必须具有相同的类型。与其他一些语言中的数组不同,Rust 中的数组具有固定的长度。
我们将数组中的值写成方括号内的逗号分隔列表:
rust
fn main() {
let a = [1, 2, 3, 4, 5];
}
当希望将数据分配在堆栈而不是堆上时,或者当希望确保始终具有固定数量的元素时,数组非常有用。但是,数组不如 vector 类型灵活。向量是标准库提供的类似集合类型,允许其大小增大或缩小。
但是,当知道元素数不需要更改时,数组会更有用。例如,如果在程序中使用月份的名称,你可能会使用数组而不是向量,因为你知道它始终包含 12 个元素:
rust
let months = ["January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December"];
使用方括号指定数组的类型,其中的元素分别是数组的类型、数组中的元素个数,如下所示:
rust
fn main() {
let a: [i32; 5] = [1, 2, 3, 4, 5];
}
你还可以初始化所有元素为相同值的数组,方法是指定初始值,后跟分号、最后是数组的长度,并将它们置于方括号内,如下所示:
rust
let a = [3; 5];
名为 a 的数组将包含 5 个元素,这些元素最初都将设置为值 3。这等同于 let a = [3, 3, 3, 3, 3];,但方式更简洁。
3.2.1.6.2.1 访问数组元素
你可以使用索引访问数组的元素,如下所示:
rust
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
在此示例中,名为 first 的变量将获得值 1,因为这是数组中索引 [0] 处的值。名为 second 的变量将从数组中的索引 [1] 获取值 2。
3.2.1.6.2.2 非法访问数组元素
让我们看看如果你尝试访问数组中超过数组末尾的元素会发生什么。假设你运行这段代码:
rust
use std::io;
fn main() {
let a = [1, 2, 3, 4, 5];
println!("Please enter an array index.");
let mut index = String::new();
io::stdin()
.read_line(&mut index)
.expect("Failed to read line");
let index: usize = index
.trim()
.parse()
.expect("Index entered was not a number");
let element = a[index];
println!("The value of the element at index {index} is: {element}");
}
如果使用 cargo run 运行此代码并输入 0、1、2、3 或 4,则程序将打印出数组中该索引处的相应值。如果输入的是一个超过数组末尾的数字,例如 10,您将看到如下输出:
bash
thread 'main' panicked at src/main.rs:19:19:
index out of bounds: the len is 5 but the index is 10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
该程序在索引操作中使用无效值时导致运行时错误。程序退出并显示错误消息,并且未执行最终的 println 语句。当你尝试使用索引访问元素时,Rust 将检查你指定的索引是否小于数组长度。如果索引大于或等于长度,Rust 将 panic。此检查必须在运行时进行,因为编译器不可能知道用户稍后运行代码时将输入什么值。
3.3 函数
fn 关键字,用来声明新函数,在函数名后跟一对花括号 {} 用来定义函数体。如:
rust
fn main() {
println!("Hello, world!");
another_function();
}
fn another_function() {
println!("Another function.");
}
Rust 不关心你在哪里定义你的函数,只关心它们在调用者可以看到的作用域中的某个位置定义。
3.3.1 函数参数
我们可以将函数定义为带有参数,如下:
rust
fn main() {
another_function(5);
}
fn another_function(x: i32) {
println!("The value of x is: {x}");
}
在函数定义中,必须声明每个参数的类型。这是 Rust 设计中的一个深思熟虑的决定:在函数定义中要求标注类型,意味着编译器几乎不需要通过查看代码中其他位置对它们的使用,来弄清楚它们的类型。如果编译器知道函数需要什么类型,它也能够提供更有用的错误消息。
定义多个参数时,请用逗号分隔参数声明,如下所示:
rust
fn main() {
print_labeled_measurement(5, 'h');
}
fn print_labeled_measurement(value: i32, unit_label: char) {
println!("The measurement is: {value}{unit_label}");
}
3.3.2 语句 和 表达式
函数体由一系列语句(statements)组成,可以选择以表达式(expression)结尾。到目前为止,我们介绍的函数尚未包含结束表达式,但您已经看到表达式作为语句的一部分。因为 Rust 是一种基于表达式的语言,所以这是一个需要理解的重要区别。其他语言没有相同的区别,所以让我们看看什么是语句和表达式,以及它们的区别如何影响函数体。
- 语句(statements):执行某些操作,但不返回值。
- 表达式(expression):表达式产生一个结果值。
由于语句(statements)不返回值,所以你不能将 let 语句分配给另一个变量,就像下面的代码尝试执行的操作一样,将会产生编译错误:
rust
fn main() {
let x = (let y = 6);
}
bash
error: expected expression, found `let` statement
--> src/main.rs:2:11
|
2 | let x = (let y = 6);
| ^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
warning: unnecessary parentheses around assigned value
--> src/main.rs:2:10
|
2 | let x = (let y = 6);
| ^ ^
|
= note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
|
2 - let x = (let y = 6);
2 + let x = let y = 6;
|
let y = 6 语句不返回值,因此 x 没有要绑定到的任何内容。这与其他语言(如 C 和 Ruby)中发生的情况不同,其中赋值返回赋值。在这些语言中,您可以编写 x = y = 6,并且 x 和 y 的值为 6;但在 Rust 中不是这种情况。
表达式(expression)的计算结果为一个值。考虑一个数学运算,例如 5 + 6,它是一个计算结果为 11 的表达式。表达式可以是语句的一部分。调用函数是一个表达式。调用宏是一个表达式。使用大括号创建的新作用域是一个表达式,例如:
rust
fn main() {
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {y}");
}
其中:
rust
{
let x = 3;
x + 1
};
是一个表达式,其计算结果为 4。该值作为 let 语句的一部分绑定到 y。请注意,x + 1 行的末尾没有分号,这与目前看到的大多数行不同。表达式不包括结束分号。如果在表达式的末尾添加分号,则会将其转换为语句,并且不会返回值。
3.3.3 函数返回值
函数可以将值返回给调用它们的代码。在函数声明的尾部用箭头 -> 声明函数返回值的类型。在 Rust 中,函数的返回值与函数体中最后一个表达式的值同义。可以通过使用 return 关键字并指定值从函数提前返回,但大多数函数都隐式返回最后一个表达式。下面是一个返回值的函数示例:
rust
fn five() -> i32 { // 函数返回值的类型为 i32
5 // 函数返回值: 5
}
fn main() {
let x = five();
println!("The value of x is: {x}");
}
函数 five() 中没有函数调用、宏,甚至没有 let 语句,只有数字 5 本身。这在 Rust 中是一个完全有效的函数。请注意,该函数的返回类型也被指定为 -> i32。尝试运行此代码,输出应如下所示:
bash
The value of x is: 5
函数 five() 中的 5 是函数的返回值,这就是为什么返回类型为 i32 的原因。让我们更详细地研究一下,有两个重要的部分:首先,语句 let x = five(); 表示正在使用函数的返回值来初始化变量。由于函数 five() 返回 5,因此该行与以下内容相同:
rust
let x = 5;
其次,函数 five()没有参数、定义了返回值的类型,但函数的主体是一个表达式 5,它作为函数的返回值。
看另外一个例子:
rust
fn main() {
let x = plus_one(5);
println!("The value of x is: {x}");
}
fn plus_one(x: i32) -> i32 {
x + 1
}
函数 plus_one() 有一个 i32 类型的参数 x,且其返回值为 i32 类型。假设将函数 plus_one() 定义修改如下:
c
fn plus_one(x: i32) -> i32 {
x + 1; // 多了一个 分号
}
将产生编译错误:
bash
error[E0308]: mismatched types
--> src/main.rs:7:24
|
7 | fn plus_one(x: i32) -> i32 {
| -------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
8 | x + 1;
| - help: remove this semicolon to return this value
For more information about this error, try `rustc --explain E0308`.
主要错误消息 mismatched types 揭示了此代码的核心问题。函数 plus_one() 的定义说明它将返回一个 i32,但语句不会计算出一个值,该值由 () 表示,即 unit 。因此,不会返回任何内容,这与函数定义相矛盾并导致错误。在此输出中,Rust 提供了一条消息,可能有助于纠正此问题:它建议删除分号,这将修复错误。
3.4 注释
Rust 中的注释:
rust
// hello, world
// So we're doing something complicated here, long enough that we need
// multiple lines of comments to do it! Whew! Hopefully, this comment will
// explain what's going on.
3.5 控制流
3.5.1 if 表达式
看一个例子:
rust
fn main() {
let number = 3;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
Rust 的 if 表达式和多数语言(如 C)不一样,它不带小括号。另外,else 表达式也不是必须的。
Rust 的 if 表达式 要是一个布尔值,如下代码将导致编译错误,因为变量 number 是一个数字:
rust
fn main() {
let number = 3;
if number {
println!("number was three");
}
}
bash
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected `bool`, found integer
修改代码成如下,将修正编译错误:
rust
fn main() {
let number = 3;
if number == 3 {
println!("number was three");
}
}
再看一下 if 处理多个分支的情形:
rust
fn main() {
let number = 6;
if number % 4 == 0 {
println!("number is divisible by 4");
} else if number % 3 == 0 {
println!("number is divisible by 3");
} else if number % 2 == 0 {
println!("number is divisible by 2");
} else {
println!("number is not divisible by 4, 3, or 2");
}
}
3.5.1.1 在 let 语句中使用 if
因为 if 是一个表达式,我们可以在 let 语句的右侧使用它,来将结果分配给一个变量。如:
rust
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("The value of number is: {number}");
}
请记住,代码块的计算结果为其中的最后一个表达式,而数字本身也是表达式。在这种情况下,整个 if 表达式的值取决于执行的代码块。这意味着有可能成为 if 的每个分支的结果的值必须为同一类型;如果类型不匹配,将遇到编译错误,如下例所示:
rust
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {number}");
}
bash
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:4:44
|
4 | let number = if condition { 5 } else { "six" };
| - ^^^^^ expected integer, found `&str`
| |
| expected because of this
if 中的表达式计算结果为整数,else 中的表达式计算结果为字符串。这是行不通的,因为变量必须只有一个类型,而 Rust 需要在编译时明确知道 number 变量是什么类型。知道 number 的类型,可以让编译器验证该类型在我们使用 number 的任何地方都有效。如果 number 的类型仅在运行时确定,Rust 将无法做到这一点;如果编译器必须跟踪任何变量的多个假设类型,则编译器将更复杂,并且对代码可靠性的保证将会更少。
3.5.2 循环
多次执行一个代码块通常很有用,对于此任务,Rust 提供了多种循环,这些循环将遍历循环体内部的代码直到结束,然后立即从头开始。
Rust 有三种类型的循环:loop、while、for,让我们逐一尝试。
3.5.2.1 loop 循环
loop 关键字告诉 Rust 永远一遍又一遍地执行一段代码,或者直到你明确告诉它停止。如:
bash
fn main() {
loop {
println!("again!");
}
}
这个循环永远不会退出,除非按下 Ctrl + C 或类似操作。幸运的是,Rust 还提供了一种使用代码跳出循环的方法,可以在循环中放置 break 关键字,以告知程序何时停止执行循环。另外,continue 关键字将导致进入下一轮循环。
3.5.2.1.1 从 loop 循环返回值
循环的用途之一是重试你知道可能会失败的操作,例如检查线程是否已完成其工作。你可能还需要将该操作的结果从循环中传递给代码的其余部分。为此,你可以在用于停止循环的 break 表达式之后添加要返回的值;该值将从循环中返回,以便你可以使用它,如下所示:
rust
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 从 loop 循环返回值
}
};
println!("The result is {result}");
}
也可以从内部循环返回,break 仅退出当前循环,而 return 始终退出当前函数。
3.5.2.1.2 loop 标签以消除多个 loop 之间的歧义
如果 loop 循环有多重,则 break 和 continue 将应用于最里层 loop。你可以选择在循环上指定一个 loop 标签,然后和 break 或 continue 一起使用,以指定它们应用于某个用 loop 标签标记的循环,而不是最内层的循环。loop 标签必须以单引号开头。下面是一个包含两个嵌套循环的示例:
rust
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}
外部循环的标签为 'counting_up,它将从 0 到 2 递增;没有标签的内循环从 10 到 9 倒计数。未指定标签的第一个 break 将仅退出内部循环;break 'counting_up; 语句将退出外部循环。编译运行代码,结果如下:
bash
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
3.5.2.2 while 循环
程序通常需要评估循环中的条件:当条件为 true 时,循环运行;当条件不再为 true 时,程序调用 break 并停止循环。可以使用 loop、if、else 和 break 的组合来实现这样的行为。然而,这种模式非常普遍,以至于 Rust 有一个内置的语言结构,称为 while 循环。示例如下:
rust
fn main() {
let mut number = 3;
while number != 0 {
println!("{number}!");
number -= 1;
}
println!("LIFTOFF!!!");
}
这个结构消除了如果你使用 loop、if、else 和 break 所必需的大量嵌套,它更清晰。当条件的计算结果为 true 时,代码将运行;否则,它将退出循环。
3.5.2.3 for 循环
你还可以使用 while 来循环访问集合的元素,例如数组。例如:
rust
fn main() {
let a = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", a[index]);
index += 1;
}
}
但是,这种方法容易出错。如果索引值或循环测试条件不正确,可能会导致程序 panic。例如,如果您将 a 数组的定义更改为具有 4 个元素,但忘记将条件更新为 while index < 4,则代码将 panic。它也很慢,因为编译器会添加运行时代码,以便在循环的每次迭代中执行索引是否在数组边界内的条件检查。
作为更简洁的替代方法,可以使用 for 循环并为集合中的每个元素执行一些代码。如:
rust
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a {
println!("the value is: {element}");
}
}
当我们运行这段代码时,我们将看到与 while 示例中相同的输出。更重要的是,我们现在提高了代码的安全性,并消除了因超出数组末尾、或没有遍历所有元素而导致错误的可能性。
使用 for 循环,如果你更改了数组的长度,你仍然不需要更改 for 循环的代码。
for 循环的安全性和简洁性使它们成为 Rust 中最常用的循环结构。即使在你想运行一些代码一定次数的情况下,比如 while 循环的倒计数示例,大多数 Rust 编程人员也会使用 for 循环。执行此操作的方法是使用标准库提供的 Range,它按顺序生成所有数字,从一个数字开始,在另一个数字之前结束。
以下是使用 for 循环和另一种我们尚未涉及的方法 rev 来反转范围的倒计数示例:
rust
fn main() {
for number in (1..4).rev() { // (1..4).rev() 反转 (1,2,3) 为 (3,2,1)
println!("{number}!");
}
println!("LIFTOFF!!!");
}
运行代码,得到如下输出:
bash
3!
2!
1!
LIFTOFF!!!