循环表达式 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() % 3 == 0 && args.len() % 5 != 0 { break 'block 1; } if args.len() % 3 != 0 && args.len() % 5 == 0 { 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;
}
}
}
}
}