文章目录
- [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!!!