
教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载本指南以 The Rust Programming Language 仓库中第三章nostarch/chapter03.md源码版见 src/ch03-00-common-programming-concepts.md为核心脉络系统讲解 Rust 中最基础的编程概念变量与可变性、标量与复合数据类型、函数、注释以及if/循环等控制流结构。读完本篇你将掌握 Rust 静态类型系统的使用习惯、表达式导向的函数设计以及如何用loop、while、for写出既安全又高效的控制流程为后续学习所有权ownership打下扎实基础。这些概念几乎出现在每一门编程语言中它们本身并非 Rust 独有但本章会在 Rust 的语境下逐一展开并说明其背后的使用约定。Rust 保留了一批关键字keywords它们只能被语言本身使用不能用作变量名或函数名完整列表见 appendix-01-keywords.md即书中附录 A。变量与可变性默认不可变编译器帮你兜底默认情况下Rust 中的变量是**不可变immutable**的这是 Rust 引导你写出更安全、更易并发代码的方式之一。当变量不可变时一旦值绑定到名字上就不能再修改。下面用一个新项目来演示。在 projects 目录下执行cargo new variables创建项目然后打开src/main.rs替换为以下代码仓库中的实际文件为 listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/src/main.rsfn main() { let x 5; println!(The value of x is: {x}); x 6; println!(The value of x is: {x}); }保存并运行cargo run会得到一个不可变性错误完整输出见 listings/ch03-common-programming-concepts/no-listing-01-variables-are-immutable/output.txt$ 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 3 | println!(The value of x is: {x}); 4 | x 6; | ^^^^^ cannot assign twice to immutable variable | help: consider making this binding mutable | 2 | let mut x 5; | 错误信息本身还给出了修复建议把绑定改成let mut x 5;。这个例子展示了编译器如何帮助你提前发现问题。遇到编译错误并不意味着你水平不行——经验丰富的 Rustacean 一样会遇到编译错误。编译器报错只说明程序还没有安全地实现你想要的行为。为什么在编译期就拦截对不可变值的修改如此重要因为这种场景极易滋生 bug如果代码的一部分假设某个值永远不会变化而另一部分悄悄改了它程序就可能无法按预期工作尤其是当第二处代码只是偶尔修改值时问题会变得极难追踪。Rust 编译器保证你说它不变它就真的不变你不需要自己操心代码也因此更容易推理。用mut显式声明可变可变性有时能让代码更简洁方便。虽然变量默认不可变但可以在变量名前加mut关键字使其可变参考第二章猜数字游戏的用法。mut同时也在向未来阅读代码的人传达意图这个变量的值会被其他部分的代码修改。修改 listings/ch03-common-programming-concepts/no-listing-02-adding-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 The value of x is: 5 The value of x is: 6是否使用可变性完全取决于你以及你认为在特定场景下哪种写法最清晰。声明常量const常量constants与不可变变量类似都是绑定到名字上且不允许改变的值但有三点关键差异常量不允许使用mut——常量不只是默认不可变而是始终不可变。声明使用const关键字而非let并且必须标注类型类型与类型注解在下一节“数据类型”中讲解。常量可以在任意作用域声明包括全局作用域因此适合存放多处代码都需要知道的值。常量只能被设置为常量表达式constant expression不能是运行时才能计算出来的结果。示例const THREE_HOURS_IN_SECONDS: u32 60 * 60 * 3;常量名为THREE_HOURS_IN_SECONDS其值是 60每分钟秒数× 60每小时分钟数× 3小时数的结果。Rust 对常量的命名约定是全大写、单词间用下划线分隔。编译器能在编译期求值有限的一组运算因此可以写成60 * 60 * 3这种易读可校验的形式而不必直接写死10800。常量的生命周期覆盖整个程序运行期间在其声明的作用域内因此很适合表示应用领域中的通用值比如游戏玩家可获得的最高分或光速。把散落在程序各处的硬编码值命名为常量既向维护者传达了值的含义也让未来需要更新该值时只需修改一处。遮蔽Shadowing正如第二章猜数字游戏中看到的你可以声明一个与之前变量同名的新变量。Rust 社区称第一个变量被第二个*遮蔽shadowed*了当你使用该名字时编译器看到的是第二个变量。实际上第二个变量覆盖了第一个把对该名字的所有使用都占为己有直到它自己被遮蔽或作用域结束。通过重复使用let关键字即可遮蔽变量对应代码见 listings/ch03-common-programming-concepts/no-listing-03-shadowing/src/main.rsfn 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}); }程序先把x绑定为5然后用let x 创建新变量取原值加1得6接着在花括号创建的内部作用域中第三个let再次遮蔽x把上一值乘以2得12。该作用域结束后内部遮蔽失效x回到6。运行输出The value of x in the inner scope is: 12 The value of x is: 6遮蔽与mut有两点本质区别不用let的赋值会报编译错误。用let可以在完成一系列转换后让变量保持不可变。遮蔽可以改变类型。因为let本质上是创建了一个新变量可以复用同一个名字但改变值的类型。例如程序让用户输入若干空格字符然后想把这些字符存成数字let spaces ; let spaces spaces.len();第一个spaces是字符串类型第二个spaces是数字类型。遮蔽让我们免于绞尽脑汁起spaces_str、spaces_num之类的名字。但如果用mut这样做见 listings/ch03-common-programming-concepts/no-listing-05-mut-cant-change-types/src/main.rs会得到类型不匹配的编译错误let mut spaces ; spaces spaces.len();error[E0308]: mismatched types -- src/main.rs:3:14 | 2 | let mut spaces ; | ----- expected due to this value 3 | spaces spaces.len(); | ^^^^^^^^^^^^ expected str, found usize错误说明不允许改变变量的类型。数据类型Rust 中每个值都属于某种数据类型data type它告诉 Rust 正在处理什么数据、如何处理。数据类型分为两个子集标量scalar和复合compound。Rust 是*静态类型statically typed*语言编译时必须知道所有变量的类型。编译器通常能根据值及其用法推断出类型但当多种类型都可能时例如第二章中把String用parse转成数字就必须加上类型注解let guess: u32 42.parse().expect(Not a number!);如果不加: u32注解Rust 会报E0284 type annotations needed错误提示编译器需要更多信息来确定类型仓库中对应的示例工程为 listings/ch03-common-programming-concepts/output-only-01-no-type-annotations。标量类型标量类型表示单个值。Rust 有四种基本标量类型整数、浮点数、布尔和字符。整数类型*整数integer*是没有小数部分的数字。第二章用到的u32表示无符号整数signed 有符号类型以i开头unsigned 无符号类型以u开头占用 32 位空间。Rust 内置的整数类型如下表表 3-1长度有符号无符号8 位i8u816 位i16u1632 位i32u3264 位i64u64128 位i128u128依架构而定isizeusize有符号与无符号的区别在于数字是否可能为负有符号数字需要携带符号正号或负号无符号数字只可能为正因此无需符号位。有符号数字以二进制补码twos complement表示。每个有符号变体可存储 −(2ⁿ⁻¹) 到 2ⁿ⁻¹ − 1 的值n 为该变体位数例如i8可存 −128 到 127无符号变体可存 0 到 2ⁿ − 1例如u8可存 0 到 255。isize和usize的位数取决于运行程序的机器架构64 位架构上是 64 位32 位架构上是 32 位。整数字面量可以写成下表表 3-2中任意形式。可以被多种数值类型解释的数字字面量允许带类型后缀如57u8数字字面量也允许用_作为视觉分隔符如1_000与1000值相同数字字面量示例十进制98_222十六进制0xff八进制0o77二进制0b1111_0000字节仅u8bA该选哪个整数类型如果不确定Rust 的默认值通常是不错的起点整数默认是i32。需要isize或usize的主要场景是索引某种集合。关于整数溢出假设有一个取值 0 到 255 的u8变量如果尝试改成超出范围的值如 256就会发生整数溢出integer overflow可能触发两种行为之一。调试模式编译时Rust 会包含整数溢出检查一旦发生溢出程序会在运行时panic程序以错误退出即称为 panicking详见 src/ch09-01-unrecoverable-errors-with-panic.md。发布模式--release标志编译时Rust不包含导致 panic 的溢出检查溢出时 Rust 执行二进制补码环绕twos complement wrapping——超过类型最大值会“绕回”到最小值u8的 256 变成 0、257 变成 1以此类推。程序不会 panic但变量值很可能不是你预期的。依赖这种环绕行为被视为错误。要显式处理溢出的可能性标准库为原始数值类型提供了四组方法wrapping_*方法如wrapping_add在所有模式下都环绕checked_*方法溢出时返回Noneoverflowing_*方法返回值并附一个表示是否溢出的布尔值saturating_*方法在最小值或最大值处饱和。浮点类型Rust 有两种原始浮点类型f3232 位和f6464 位。默认类型是f64因为在现代 CPU 上它和f32速度大致相同但精度更高。所有浮点类型都是有符号的遵循 IEEE-754 标准表示fn main() { let x 2.0; // f64 let y: f32 3.0; // f32 }数值运算Rust 支持所有数值类型的基本数学运算加、减、乘、除、取余。整数除法向零截断。对应代码见 listings/ch03-common-programming-concepts/no-listing-07-numeric-operations/src/main.rsfn 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; }其中每个表达式都使用数学运算符求值为单个值并绑定给一个变量。Rust 提供的全部运算符列表见 appendix-02-operators.md附录 B。布尔类型布尔类型有两个可能的值true和false占用 1 字节类型名是bool代码见 listings/ch03-common-programming-concepts/no-listing-08-boolean/src/main.rsfn main() { let t true; let f: bool false; // with explicit type annotation }布尔值主要通过条件如if表达式使用详见后文“控制流”。字符类型Rust 的char类型是最基本的字母类型代码见 listings/ch03-common-programming-concepts/no-listing-09-char/src/main.rsfn main() { let c z; let z: char ℤ; // with explicit type annotation let heart_eyed_cat ; }注意char字面量用单引号字符串字面量用双引号。Rust 的char类型占 4 字节表示一个 Unicode 标量值Unicode scalar value因此远不止 ASCII带重音字母、中日韩文字、emoji、零宽空格都是合法的char值。Unicode 标量值范围为U0000到UD7FF及UE000到U10FFFF。不过“字符”在 Unicode 中并非一个真正的概念你直觉上理解的“字符”可能与 Rust 的char并不一致详见 src/ch08-02-strings.md 中“用字符串存储 UTF-8 编码文本”一节。复合类型复合类型可以把多个值组合成一个类型。Rust 有两种原始复合类型元组和数组。元组Tuple元组是把多个不同类型的值组合成一个复合类型的通用方式。元组长度固定声明后既不能增长也不能缩小。元组写法是用括号写出逗号分隔的值列表每个位置有自己的类型各位置类型不必相同fn main() { let tup: (i32, f64, u8) (500, 6.4, 1); }变量tup绑定的是整个元组因为元组被视为单个复合元素。取单个值有两种方式方式一用模式匹配解构destructure对应 listings/ch03-common-programming-concepts/no-listing-11-destructuring-tuples/src/main.rsfn main() { let tup (500, 6.4, 1); let (x, y, z) tup; println!(The value of y is: {y}); }程序先用let配合模式把tup拆成x、y、z三个独立变量即解构再打印y的值6.4。方式二用句点加索引直接访问对应 listings/ch03-common-programming-concepts/no-listing-12-tuple-indexing/src/main.rsfn 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; }与大多数语言一样元组的第一个索引是 0。没有任何值的元组有个特殊名字单元unit。该值和对应类型都写作()表示空值或空返回类型。表达式若没有返回其他值就隐式返回单元值。数组Array另一种容纳多个值的方式是数组。与元组不同数组的每个元素必须是同一类型与某些其他语言的数组不同Rust 数组长度固定fn main() { let a [1, 2, 3, 4, 5]; }当你希望数据分配在栈上栈与堆的讨论见 src/ch04-01-what-is-ownership.md或希望确保元素数量固定时数组很有用。但数组不如标准库的vector灵活——vector 的内容在堆上允许增长或缩小。如果不确定该用数组还是 vector大概率应该用 vector详见 src/ch08-01-vectors.md。但当元素数量确定不变时数组更合适例如月份名永远有 12 个let months [January, February, March, April, May, June, July, August, September, October, November, December];数组类型用方括号书写元素类型、分号、元素个数let a: [i32; 5] [1, 2, 3, 4, 5];这里i32是元素类型分号后的5表示数组含 5 个元素。还可以用[初始值; 长度]的简写把每个元素都初始化为同一个值let a [3; 5];这等价于let a [3, 3, 3, 3, 3];但更简洁。访问数组元素数组是一块已知、固定大小的连续内存可以分配在栈上。用索引访问元素fn main() { let a [1, 2, 3, 4, 5]; let first a[0]; let second a[1]; }first得到索引[0]处的值1second得到索引[1]处的值2。无效的数组访问Rust 内存安全的体现看看如果访问越界会发生什么。运行下面的代码对应 listings/ch03-common-programming-concepts/no-listing-15-invalid-array-access/src/main.rs类似第二章猜数字游戏那样让用户输入索引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}); }这段代码能正常编译。输入0~4会打印对应元素但如果输入越界的10会看到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_BACKTRACE1 environment variable to display a backtrace程序在索引操作使用无效值时产生运行时错误并以错误信息退出不会执行最后的println!。原因是用索引访问元素时Rust 会检查索引是否小于数组长度索引大于等于长度就会 panic。这个检查必须在运行时进行——编译器不可能知道用户以后运行时会输入什么值。这正是 Rust 内存安全原则的体现许多底层语言不做这种检查错误的索引会访问到无效内存Rust 则通过立即退出而非允许内存访问继续执行来保护你。更深入的错误处理见 src/ch09-00-error-handling.md。函数函数在 Rust 代码中无处不在。你已经见过语言中最重要的函数之一main函数它是很多程序的入口也见过声明新函数的fn关键字。Rust 代码使用snake case作为函数和变量名的惯例风格全小写、单词间用下划线分隔。示例fn main() { println!(Hello, world!); another_function(); } fn another_function() { println!(Another function.); }定义函数fn后跟函数名和一对括号花括号告诉编译器函数体的起止。调用函数函数名后跟一对括号。another_function定义在main之后但这完全没关系——Rust 不关心函数定义在哪里只关心它定义在调用者可见的某个作用域内。新建一个名为functions的二进制项目cargo new functions把上面的例子放进src/main.rs运行$ cargo run Hello, world! Another function.行按main中出现的顺序执行先打印 “Hello, world!”再调用another_function打印它的消息。参数Parameters函数可以定义参数即函数签名中的特殊变量。调用时传入的具体值称为实参arguments但日常交流中人们常把 parameter 和 argument 混用。带参数的版本对应 listings/ch03-common-programming-concepts/no-listing-17-functions-with-parameters/src/main.rsfn main() { another_function(5); } fn another_function(x: i32) { println!(The value of x is: {x}); }输出The value of x is: 5another_function声明了一个名为x、类型为i32的参数。传入5后println!宏把5填进格式字符串中{x}的位置。函数签名中必须声明每个参数的类型这是 Rust 设计上的刻意决定函数定义要求类型注解意味着编译器几乎不需要你在其他地方标注类型而且编译器在知道函数期望类型后能给出更有用的错误信息。多个参数用逗号分隔fn main() { print_labeled_measurement(5, h); } fn print_labeled_measurement(value: i32, unit_label: char) { println!(The measurement is: {value}{unit_label}); }运行输出The measurement is: 5h。语句与表达式函数体由一系列语句组成可选地以表达式结尾。Rust 是基于表达式的语言expression-based区分两者非常重要语句statements执行某种操作、不返回值表达式expressions求值为一个结果值。let y 6;就是一条语句见 listings/ch03-common-programming-concepts/no-listing-19-statements-vs-expressions/src/main.rs 对应的输出工程。函数定义本身也是语句。语句不返回值因此不能把let语句赋值给另一个变量fn main() { let x (let y 6); }运行会得到error: expected expression, found let statement。这不同于 C、Ruby 等语言它们允许x y 6让两个变量都等于 6Rust 中做不到。表达式求值为值构成你写的其余大部分代码。5 6是求值为11的表达式let y 6;中的6是表达式调用函数是表达式调用宏是表达式用花括号创建的新作用域块也是表达式fn main() { let y { let x 3; x 1 }; println!(The value of y is: {y}); }这个块表达式求值为4作为let语句的一部分绑定给y。注意x 1这一行末尾没有分号——表达式不以分号结尾一旦加上分号表达式就变成语句不再返回值。这一点在理解函数返回值时至关重要。带返回值的函数函数可以向调用它的代码返回值。我们不命名返回值但必须在箭头-后声明返回类型。在 Rust 中函数返回值等同于函数体最后一个表达式的值。也可以用return关键字提前返回并指定值但大多数函数隐式返回最后一个表达式。示例对应 listings/ch03-common-programming-concepts/no-listing-21-function-return-values/src/main.rsfn five() - i32 { 5 } fn main() { let x five(); println!(The value of x is: {x}); }five函数体里没有函数调用、宏甚至let语句只有一个孤零零的5——这在 Rust 中是完全合法的函数。返回类型是- i32。let x five();等价于let x 5;。five函数体中的5没有分号因为它是一个我们想返回其值的表达式。再看一个例子fn main() { let x plus_one(5); println!(The value of x is: {x}); } fn plus_one(x: i32) - i32 { x 1 }运行打印The value of x is: 6。但如果在x 1后面加上分号把它从表达式变成语句呢fn plus_one(x: i32) - i32 { x 1; }编译会报错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核心错误是mismatched typesplus_one声明返回i32但语句不求值表现为单元类型()因此什么都没返回与函数定义矛盾。错误输出中还给出了修复建议去掉分号。注释程序员都努力让代码易于理解但有时确实需要额外解释。这时在源码中留下注释——编译器会忽略、但阅读源码的人可能受益// hello, worldRust 惯用的注释风格是两个斜杠开头注释延续到行尾。多行注释需要每行都写//// So were doing something complicated here, long enough that we need // multiple lines of comments to do it! Whew! Hopefully, this comment will // explain whats going on.注释也可以放在含代码的行尾对应 listings/ch03-common-programming-concepts/no-listing-24-comments-end-of-line/src/main.rsfn main() { let lucky_number 7; // Im feeling lucky today }但更常见的写法是把注释放在它所注解的代码上方单独一行对应 listings/ch03-common-programming-concepts/no-listing-25-comments-above-line/src/main.rsfn main() { // Im feeling lucky today let lucky_number 7; }Rust 还有另一种注释——文档注释documentation comments详见 src/ch14-02-publishing-to-crates-io.md 中“发布 crate 到 crates.io”一节。控制流根据条件是否为true执行某些代码以及在条件为true时重复执行某些代码是大多数编程语言的基本构件。Rust 中最常见的控制执行流的结构是if表达式和循环。if 表达式if表达式让你根据条件分支代码。新建名为branches的项目在src/main.rs中输入fn main() { let number 3; if number 5 { println!(condition was true); } else { println!(condition was false); } }所有if表达式都以if关键字开头后跟条件。此处条件检查number是否小于 5。条件为true时要执行的代码块紧跟在条件之后、放在花括号内。与if表达式条件关联的代码块有时称为arm分支类似第二章match表达式的 arm。可选地可以加else表达式提供条件为false时执行的备选代码块不提供else时条件为假则跳过if块继续执行。运行输出condition was true把number改成7输出变为condition was false。条件必须是bool类型。如果条件不是bool会报错。例如fn main() { let number 3; if number { println!(number was three); } }error[E0308]: mismatched types -- src/main.rs:4:8 | 4 | if number { | ^^^^^^ expected bool, found integer与 Ruby、JavaScript 不同Rust不会自动把非布尔类型转换为布尔值你必须显式给if提供布尔条件。想让if代码块在数字不等于 0 时执行要写成fn main() { let number 3; if number ! 0 { println!(number was something other than zero); } }用 else if 处理多个条件把if和else组合成else if即可处理多个条件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); } }这个程序有四个可能的路径运行输出number is divisible by 3。程序按顺序检查每个if执行第一个条件为true的分支体。注意虽然 6 能被 2 整除却看不到number is divisible by 2也看不到else块的输出——Rust 只执行第一个true条件的块找到后就不再检查其余条件。else if用多了会使代码臃肿如果超过一个建议考虑重构。第六章将介绍 Rust 更强大的分支结构match见 src/ch06-02-match.md。在 let 语句中使用 if因为if是表达式可以放在let语句右侧把结果赋给变量fn main() { let condition true; let number if condition { 5 } else { 6 }; println!(The value of number is: {number}); }number会根据if表达式的结果绑定值运行输出The value of number is: 5。代码块求值为其中的最后一个表达式数字本身也是表达式。整个if表达式的值取决于执行哪个分支块因此每个分支可能产生的结果必须是同一类型——上例中if分支和else分支都是i32。类型不匹配会报错fn main() { let condition true; let number if condition { 5 } else { six }; println!(The value of number is: {number}); }error[E0308]: if and else have incompatible types -- src/main.rs:4:44 | 4 | let number if condition { 5 } else { six }; | - ^^^^^ expected integer, found strif块求值为整数、else块求值为字符串这行不通因为变量必须只有一个类型Rust 需要在编译期确定number的类型。如果类型只能到运行时才知道编译器就不得不跟踪变量的多种假设类型既更复杂也无法对代码做出足够多的保证。用循环重复执行Rust 提供几种循环执行完循环体后立即回到开头。新建名为loops的项目来实验。Rust 有三种循环loop、while和for。用 loop 重复代码loop关键字让 Rust 反复执行代码块要么永远执行要么直到你显式叫停。例如对应 listings/ch03-common-programming-concepts/no-listing-32-loop/src/main.rsfn main() { loop { println!(again!); } }运行后again!会不停打印直到手动停止。大多数终端支持ctrl-C快捷键中断陷入死循环的程序$ cargo run Compiling loops v0.1.0 (file:///projects/loops) Finished dev profile [unoptimized debuginfo] target(s) in 0.08s Running target/debug/loops again! again! again! again! ^Cagain!^C代表你按下ctrl-C的位置。^C之后是否还出现again!取决于收到中断信号时循环执行到哪里。Rust 也提供了用代码跳出循环的方式在循环内使用break关键字告诉程序何时停止执行循环。第二章猜数字游戏中就在用户猜对后用它退出程序。continue则跳过本次迭代剩余代码直接进入下一次迭代。从循环返回值loop的一个用途是重试你可能知道会失败的操作比如检查线程是否完成任务并把操作结果带出循环交给后续代码。为此可以在用来停止循环的break表达式后面加上想返回的值该值会从循环中返回出来fn main() { let mut counter 0; let result loop { counter 1; if counter 10 { break counter * 2; } }; println!(The result is {result}); }循环前声明counter并初始化为0再声明result存放循环返回值。每次迭代counter加 1检查是否等于 10等于时用break携带counter * 2退出。循环结束后用分号结束赋值语句最后打印result本例为20。也可以从循环内部returnbreak只退出当前循环return则始终退出当前函数。用循环标签消歧循环嵌套时break和continue作用于当前最内层循环。可以给循环指定循环标签loop label再用break/continue指定它们作用于哪个被标记的循环。循环标签必须以单引号开头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;退出外层循环。运行输出count 0 remaining 10 remaining 9 count 1 remaining 10 remaining 9 count 2 remaining 10 End count 2用 while 精简条件循环程序经常需要在循环内求值条件条件为true时循环运行变为false时调用break停止。用loop、if、else、break组合可以实现这种模式但该模式太常见Rust 为此内置了while循环构造。下面的例子用while让程序循环三次每次倒计时循环结束后打印消息并退出fn main() { let mut number 3; while number ! 0 { println!({number}!); number - 1; } println!(LIFTOFF!!!); }这个构造消除了用loop/if/else/break组合时的大量嵌套更清晰条件为true时运行代码否则退出循环。用 for 遍历集合也可以用while遍历集合元素比如数组。下面的循环打印数组a的每个元素fn main() { let a [10, 20, 30, 40, 50]; let mut index 0; while index 5 { println!(the value is: {}, a[index]); index 1; } }代码从索引 0 开始一直循环到index 5不再为 true即到达数组最后索引。运行会打印全部五个元素。但这种方式容易出错如果索引值或测试条件不正确程序可能 panic。例如把a改成四个元素却忘了把条件改成while index 4代码就会 panic。它也比较慢因为编译器会加入运行时代码在每次迭代执行索引是否在数组边界内的条件检查。更简洁的替代方案是for循环——对集合中的每个元素执行某些代码fn main() { let a [10, 20, 30, 40, 50]; for element in a { println!(the value is: {element}); } }输出与while版本相同但安全性提高彻底消除了越界或漏掉元素的 bug 隐患而且for循环生成的机器码可能更高效因为索引无需在每次迭代与数组长度比较。for循环下即使改变数组元素数量也不需要像 Listing 3-4 那样去改其他代码。for循环的安全与简洁使其成为 Rust 中最常用的循环构造。即使想执行固定次数像上面while倒计时那样多数 Rustacean 也会用for配合标准库提供的Range范围——它从起始数字开始、在另一个数字之前结束生成一列数字。用for和rev反转范围实现倒计时fn main() { for number in (1..4).rev() { println!({number}!); } println!(LIFTOFF!!!); }这段代码是不是清爽多了总结本章内容相当可观你学习了变量、标量与复合数据类型、函数、注释、if表达式以及三种循环。要练习本章概念可以试着编写以下程序在华氏温度与摄氏温度之间互相转换生成第n个斐波那契数打印圣诞颂歌《The Twelve Days of Christmas》的歌词充分利用歌曲中的重复。完成这些练习后就可以进入 Rust 中一个不常见于其他语言的独特概念所有权ownership见 src/ch04-00-understanding-ownership.md。赞分享教程文档【免费下载链接】bookThe Rust Programming Language项目地址https://gitcode.com/gh_mirrors/bo/book点击查看免费下载相关推荐3 行配置用 kkFileView CAD 批注在浏览器里直接上手画图纸3 行配置用 kkFileView CAD 批注在浏览器里直接上手画图纸 看 DWG 总得先打开 CAD用 kkFileView CAD 批注浏览器里就能后端SQLModel 数据库入门从数据库概念到 SQL 关系模型的完整指南SQLModel 数据库入门从数据库概念到 SQL 关系模型的完整指南 本指南基于 SQLModel 官方文档《Intro to Databases》整理面ORM数据库后端90DaysOfDevOps 实战用 Go 编写变量、常量与数据类型入门程序Day 1190DaysOfDevOps 实战用 Go 编写变量、常量与数据类型入门程序Day 11 本篇技术指南是 90DaysOfDevOps 学习路径中 Go文档/教程上一篇告别打字疲劳Joplin触控笔手写输入全攻略下一篇终极免费离线绘图工具draw.io桌面版完全使用指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考