循环表达式 Loop
Rust 支持四种循环表达式的写法, 下面列出它们的基本语法:
loop {
block
}
for pattern in iterator {
block
}
while condition {
block
}
while let pattern = expr {
block
}
每种写法都应各自的使用场景, 本节会依次介绍它们.
loop 循环
最简单的循环语句就是 loop { block }, 它相当于 C 语言中的:
while (true) {
block
}
但是, Rust 单独引入了一个 loop 关键字来表示一个无限循环语句.
终止无限循环的方法也有几种:
break表达式, 立即终止循环return表达式, 立即终止循环并退出当前函数- 抛出错误, 立即终止循环. 退出当前函数并将错误向上继续抛出
- 抛出 panic, 当前线程直接终止
- 调用
std::process::exit()退出程序
使用 return 表达式终止循环的例子:
fn fibonacci(mut n: i32) -> i32 {
debug_assert!(n >= 0);
let mut x: i32 = 0;
let mut y: i32 = 1;
loop {
(x, y) = (x + y, x);
n -= 1;
if n == 0 {
return x;
}
}
}
fn main() {
assert_eq!(fibonacci(1), 1);
assert_eq!(fibonacci(2), 1);
assert_eq!(fibonacci(10), 55);
}
如果 loop 循环的内部代码块执行时产生了错误 (Result<T, E>), 该错误又没有在代码块内部捕获,
而是将错误向函数调用处抛出了, 那么就会立即终止当前的循环.
下面的示例程序会尝试读取 shadow 文件, 但因为没有读取权限, 就会产生 io::Error, 进而终止整个循环:
use std::fs;
use std::io;
use std::thread;
use std::time::Duration;
fn read_files() -> io::Result<()> {
let mut is_shadow = false;
loop {
let filepath = if is_shadow {
"/etc/shadow"
} else {
"/etc/passwd"
};
// 当尝试读取 shadow 文件 (当前用户无权读取) 时, 会终止无限循环.
let content = fs::read_to_string(filepath)?;
assert!(!content.is_empty());
thread::sleep(Duration::from_secs(1));
is_shadow = !is_shadow;
}
}
fn main() {
let ret = read_files();
assert!(ret.is_err());
assert_eq!(ret.err().unwrap().kind(), io::ErrorKind::PermissionDenied);
}
使用 break 终止循环
break 表达式的语法如下:
break [LifeTime | Label] [Expression]
可以发现 break 表达式比在其它语言中要更为复杂, 它后面通常都留空, 只立即终止当前循环; 但也可以跟随标签(label) 或者表达式
fn main() {
let mut count = 0;
loop {
count += 1;
if count == 3 {
println!("three");
}
if count == 5 {
println!("five");
break;
}
}
}
break 跳转到最外层循环
多层嵌套的循环语句, 可以使用 break Label 跳出来.
fn main() {
let mut sum = 0;
'outer:
for i in 0..100 {
for j in 0..i {
sum += j;
if i * j > 200 {
break 'outer;
}
}
}
assert_eq!(sum, 560);
#[allow(clippy::never_loop)]
#[allow(unused_labels)]
'outer_loop: loop {
println!("Enter the outer loop");
'inner_loop: loop {
println!("Enter the inner loop");
break 'outer_loop;
}
}
println!("Leave the outer loop");
}
循环中使用 break 来返回值
loop 表达式也可以有返回值:
fn main() {
let mut x: i32 = 0;
let mut y: i32 = 1;
let accum: i32 = loop {
(x, y) = (x + y, x);
if x > 30 {
break x + y;
}
};
assert_eq!(accum, 55);
}
代码块使用 break 来返回值
use std::env;
fn main() {
let args: Vec<String> = env::args().collect();
let result: i32 = 'block: {
if args.len().is_multiple_of(3) && !args.len().is_multiple_of(5) {
break 'block 1;
}
if !args.len().is_multiple_of(3) && args.len().is_multiple_of(5) {
break 'block 2;
}
3
};
assert_eq!(result, 3);
}
深入理解 break 表达式
先看一个基于 RustQuiz#20 修改的示例程序, 考虑考虑程序运行的结果是什么样的:
#![allow(clippy::never_loop)]
#![allow(unreachable_code)]
#![allow(clippy::unused_unit)]
fn break1() {
loop {
if (break println!("1")) {
let _ = 1;
}
}
}
fn break1_expanded() {
loop {
if (break {
println!("1");
}) {
let _ = 1;
}
}
}
#[rustfmt::skip]
fn break2() {
loop {
if break { println!("2") }
{
let _ = 2;
}
}
}
fn break2_expanded() {
loop {
if break () {
println!("2")
}
// Another unused block
{
let _ = 2;
}
}
}
fn break3() {
loop {
if break println!("3") {
let _ = 3;
}
}
}
fn noop() {}
fn main() {
break1();
break1_expanded();
break2();
break2_expanded();
noop();
break3();
}
上面的代码中, break1_expand() 函数是对 break1() 的重新格式化, 这样更容易阅读:
break { println!("1"); }这个表达式作为if表达式的条件, 会优先被执行, 会打印出1- 它执行的结果是
(), 所以if表达式中的条件不成立, if 表达式内的代码块不会被执行 - 然后立即终止本循环
可以看一下它的 MIR 代码:
fn break1() -> () {
let mut _0: ();
let _1: ();
let mut _2: std::fmt::Arguments<'_>;
let mut _3: &[&str];
let mut _4: &[&str; 1];
scope 1 {
}
bb0: {
_4 = const break1::promoted[0];
_3 = _4 as &[&str] (PointerCoercion(Unsize));
_2 = Arguments::<'_>::new_const(move _3) -> [return: bb1, unwind continue];
}
bb1: {
_1 = _print(move _2) -> [return: bb2, unwind continue];
}
bb2: {
return;
}
}
const break1::promoted[0]: &[&str; 1] = {
let mut _0: &[&str; 1];
let mut _1: [&str; 1];
bb0: {
_1 = [const "1\n"];
_0 = &_1;
return;
}
}
而 break2() 就更奇怪了, 它与 break() 相比, 只是少了一对小括号. break2_expanded() 是它的展开样式,
可以发现 if break () { xxx } 表达式是核心, break () 表达式返回值为空, 所以 if 表达式条件判断不成立,
if 语句内的代码块不会被执行.
break2() 和 break2_expanded() 的 MIR 代码如下:
fn break2() -> () {
let mut _0: ();
scope 1 {
}
bb0: {
return;
}
}
fn break2_expanded() -> () {
let mut _0: ();
scope 1 {
}
bb0: {
return;
}
}
可以看出来, 这两个函数其实什么都不会做的, 类似于 noop() 函数:
fn break3() -> () {
let mut _0: ();
let _1: ();
let mut _2: std::fmt::Arguments<'_>;
let mut _3: &[&str];
let mut _4: &[&str; 1];
scope 1 {
跳过当前循环 continue
在循环语句中使用 continue 来跳过当前循环中的后续代码, 继续执行下个循环.
continue 表达式的语法如下:
continue [Lifetime | Label ]
可以看到, continue 表达式也是支持标签的, 用于快速跳出多层循环嵌套.
先看一个例子, 展示 continue 的一般用法:
fn main() {
for i in 1..20 {
if i % 15 == 0 {
println!("factor of 15");
continue;
}
if i % 5 == 0 {
println!("factor of 5");
continue;
}
if i % 3 == 0 {
println!("factor of 3");
}
}
}
下面的例子, 展示了如何使用 continue Label 跳出多层循环:
fn main() {
let mut sum = 0;
'outer:
for i in 0..100 {
for j in 0..i {
sum += j;
if i * j > 200 {
continue 'outer;
}
}
}
assert_eq!(sum, 2087);
}
for 循环
for .. in 表达式用于遍历一个迭代器.
fn main() {
for i in 1..10 {
println!("{i}^2 = {}", i * i);
}
}
默认情况下, for 在遍历一个集合时会使用 Iterator trait 的 into_iter() 方法.
除了这个方法之外, 还有另外两个方法:
iter()以引用的方法遍历集合, 不改变集合中的值, 该容器接下来还可以被使用into_iter()从集合中解析出里面的数据, 一旦遍历完它, 该集合接下来不可再被使用, 相当于把这个集合move到了这个循环中iter_mut()以可变引用的方法遍历集合, 可以改变集合中的值, 该集合在接下来还可被使用
while 循环
while 的一般写法跟在 C/C++ 语言中没有多少差别, 当条件成立时, 就执行内部的代码块; 当条件不成立时, 就终止循环. 看一个小示例:
fn main() {
let mut num = 1;
while !(num % 3 == 0 && num % 5 == 0) {
num += 1;
}
assert_eq!(num, 15);
}
另一个小示例, 猜数字:
use std::io;
fn read_number() -> i32 {
let mut line = String::new();
if io::stdin().read_line(&mut line).is_err() {
0
} else {
line.trim().parse::<i32>().unwrap_or_default()
}
}
fn main() {
println!("Guess number!");
while read_number() != 42 {
println!("Try again");
}
println!("You have got it!");
}
while let 循环
while let 表达式用于支持模式匹配, 当匹配成功时, 会执行 while 语句内的代码; 如果匹配失败了, 就终止循环.
下面的示例程序展示了单链表的一种写法, 注意里面的 len() 函数和 debug_print() 函数, 它们展示了 while let 的用法:
#[derive(Debug, Clone)]
pub struct ListNode {
pub val: i32,
pub next: Option<Box<ListNode>>,
}
impl ListNode {
#[must_use]
pub fn len(head: &Option<Box<Self>>) -> usize {
let mut node_ref = head;
let mut count = 0;
while let Some(node_box) = node_ref {
node_ref = &node_box.as_ref().next;
count += 1;
}
count
}
#[must_use]
#[inline]
pub const fn is_empty(head: &Option<Box<Self>>) -> bool {
head.is_none()
}
#[must_use]
pub fn from_slice(slice: &[i32]) -> Option<Box<Self>> {
let mut head = None;
for &val in slice.iter().rev() {
head = Some(Box::new(Self {
val,
next: head,
}))
}
head
}
fn debug_print(head: &Option<Box<Self>>) {
print!("head: [ ");
let mut node_ref = head;
while let Some(node_box) = node_ref {
let val: i32 = node_box.as_ref().val;
print!("{val}, ");
node_ref = &node_box.as_ref().next;
}
println!("]");
}
}
fn main() {
let list = ListNode::from_slice(&[1, 2, 3, 5, 8]);
ListNode::debug_print(&list);
assert_eq!(ListNode::len(&list), 5);
}
为什么引入 loop 表达式
上文已经介绍了 loop 和 while 表达式, 那么问题来了, 既然 loop { block } 就相当于 while true { block },
那为什么 Rust 还要单独引入一个新的关键字呢? 像 C/C++ 这样的语言并不需要这样.
先看一个示例代码:
fn fibonacci(mut n: i32) -> i32 {
debug_assert!(n >= 1);
let mut x = 1;
let mut y = 0;
// 换成 while true 后就会编译失败.
// while true {
loop {
(x, y) = (x + y, x);
n -= 1;
if n == 1 {
return x;
}
}
}
fn main() {
assert_eq!(fibonacci(10), 55);
}
上面的 fibonacci() 函数中, 如果把 loop 换成 while true, 就会编译失败.
这个是 rustc 编译器比较特殊的地方, 因为它支持 flow-sensitive analysis. 在 if/while 等支持条件判断的语句中,
它不会直接判断 condition 表达式的值是 true 还是 false; 它会假设条件的值既可以是 true, 也可以是 false,
然后继续分析 if/while 语句内部的代码块. 很显然, 上面的代码中, 如果把 loop 换成 while true,
当 rustc 编译器检查代码时, 它就会发现 fibonacci() 函数在不同的分支判断情况下可能返回不同类型的值,
而这是不被允许的.
遇到这样的情况, 就直接用 loop 表达式.
同样的, 看另一个示例, 它来自某个服务器端模块代码, 初始化好之后, 服务就开始一直运行下去了, 直到整个进程退出,
run_loop() 函数的返回值类型很不常见, ! 表示 never type.
impl Server {
pub async fn run_loop(&mut self) -> ! {
loop {
tokio::select! {
Some(cmd) = self.dispatcher_receiver.recv() => {
if let Err(err) = self.handle_dispatcher_cmd(cmd).await {
log::error!("Failed to handle dispatcher cmd: {:?}", err);
}
}
Some(cmd) = self.server_ctx_receiver.recv() => {
self.handle_server_ctx_cmd(cmd).await;
}
}
}
}
}