Rust 学习笔记 3:一般性编程概念

上一篇:Rust 学习笔记 2:猜数字游戏

文章目录

  • [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.2 `while` 循环](#3.5.2.2 while 循环)
        • [3.5.2.3 `for` 循环](#3.5.2.3 for 循环)

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 的浮点型有 f32f64 两种,默认位 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 中的布尔类型有两个可能的值:truefalse布尔值的大小为 1 个字节Rust 中的 布尔类型是使用 bool 指定的。例如:

rust 复制代码
fn main() {
	let t = true;
 
	let f: bool = false; // with explicit type annotation
}
3.2.1.5 字符类型(The Character Type)

Rustchar 类型是该语言最原始的字母类型。以下是声明 char 值的一些示例:

rust 复制代码
fn main() {
	let c = 'z';
	let z: char = 'ℤ'; // with explicit type annotation
	let heart_eyed_cat = '😻';
}

请注意,我们使用单引号指定 char 文本,而不是使用双引号的字符串文本。Rustchar 类型大小为 4 个字节,表示一个 Unicode 标量值,这意味着它可以表示的不仅仅是 ASCII。重音字母、中文、日文和韩文字符、表情符号、和零宽度的空格都是 Rust 中的有效 char 值。Unicode 标量值的范围U+0000 到 U+D7FFU+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 将检查你指定的索引是否小于数组长度。如果索引大于或等于长度,Rustpanic此检查必须在运行时进行,因为编译器不可能知道用户稍后运行代码时将输入什么值

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 没有要绑定到的任何内容。这与其他语言(如 CRuby)中发生的情况不同,其中赋值返回赋值。在这些语言中,您可以编写 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");
	}
}

Rustif 表达式和多数语言(如 C)不一样,它不带小括号。另外,else 表达式也不是必须的

Rustif 表达式 要是一个布尔值,如下代码将导致编译错误,因为变量 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 循环有多重,则 breakcontinue 将应用于最里层 loop。你可以选择在循环上指定一个 loop 标签,然后和 breakcontinue 一起使用,以指定它们应用于某个用 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!!!
相关推荐
SomeB1oody1 天前
【Rust自学】4.1. 所有权:栈内存 vs. 堆内存
开发语言·后端·rust
SomeB1oody2 天前
【Rust自学】4.2. 所有权规则、内存与分配
开发语言·后端·rust
SomeB1oody2 天前
【Rust自学】4.5. 切片(Slice)
开发语言·后端·rust
编码浪子2 天前
构建一个rust生产应用读书笔记6-拒绝无效订阅者02
开发语言·后端·rust
baiyu332 天前
1小时放弃Rust(1): Hello-World
rust
baiyu332 天前
1小时放弃Rust(2): 两数之和
rust
Source.Liu2 天前
数据特性库 前言
rust·cad·num-traits
编码浪子2 天前
构建一个rust生产应用读书笔记7-确认邮件1
数据库·rust·php
SomeB1oody2 天前
【Rust自学】3.6. 控制流:循环
开发语言·后端·rust
Andrew_Ryan2 天前
深入了解 Rust 核心开发团队:这些人如何塑造了世界上最安全的编程语言
rust