Loops and other breakable expressions

循环和其他可中断表达式

loop-expr.md
commit: aa9c70bda63b3ab73b15746609831dafb96f56ff
本章译文最后维护日期:2023-05-03

句法
LoopExpression :
   LoopLabel? (
         InfiniteLoopExpression
      | PredicateLoopExpression
      | PredicatePatternLoopExpression
      | IteratorLoopExpression
      | LabelBlockExpression
   )

Rust支持五种循环表达式:

所有五种类型的循环都支持 break表达式循环标签(label). 除了带标签的块表达式都支持 continue表达式。 只有 loop循环和带标签的块表达式支持非平凡求值(evaluation to non-trivial values)1

Infinite loops

无限循环

句法
InfiniteLoopExpression :
   loop BlockExpression

loop表达式会不断地重复地执行它代码体内的代码:loop { println!("I live."); }

没有包含关联的 break表达式的 loop表达式是发散的,并且具有类型 !。 包含相应 break表达式的 loop表达式可以结束循环,并且此表达式的类型必须与 break表达式的类型兼容。

Predicate loops

谓词循环

句法
PredicateLoopExpression :
   while Expression排除结构体表达式 BlockExpression

while循环从对布尔型的循环条件操作数求值开始。 如果循环条件操作数的求值结果为 true,则执行循环体块,然后控制流返回到循环条件操作数。如果循环条件操作数的求值结果为 false,则 while表达式完成。

举个例子:

#![allow(unused)]
fn main() {
let mut i = 0;

while i < 10 {
    println!("hello");
    i = i + 1;
}
}

Predicate pattern loops

谓词模式循环

句法
PredicatePatternLoopExpression :
   while let Pattern = Scrutinee排除惰性布尔运算符表达式 BlockExpression

while let循环在语义上类似于 while循环,但它用 let关键字后紧跟着一个模式、一个 =、一个检验对象(scrutinee)表达式和一个块表达式,来替代原来的条件表达式。 如果检验对象表达式的值与模式匹配,则执行循环体块,然后控制流再返回到模式匹配语句。如果不匹配,则 while表达式执行完成。

#![allow(unused)]
fn main() {
let mut x = vec![1, 2, 3];

while let Some(y) = x.pop() {
    println!("y = {}", y);
}

while let _ = 5 {
    println!("不可反驳模式总是会匹配成功");
    break;
}
}

while let循环等价于包含匹配(match)表达式的 loop表达式。 如下:

'label: while let PATS = EXPR {
    /* loop body */
}

等价于

'label: loop {
    match EXPR {
        PATS => { /* loop body */ },
        _ => break,
    }
}

可以使用操作符 | 指定多个模式。 这与匹配(match)表达式中的 | 具有相同的语义:

#![allow(unused)]
fn main() {
let mut vals = vec![2, 3, 1, 2, 2];
while let Some(v @ 1) | Some(v @ 2) = vals.pop() {
    // 打印 2, 2, 然后 1
    println!("{}", v);
}
}

if let表达式的情况一样,检验表达式不能是一个懒惰布尔运算符表达式

Iterator loops

迭代器循环

句法
IteratorLoopExpression :
   for Pattern in Expression排除结构体表达式 BlockExpression

for表达式是一个用于在 std::iter::IntoIterator 的某个迭代器实现提供的元素上进行循环的语法结构。 如果迭代器生成一个值,该值将与此 for表达式提供的不可反驳型模式进行匹配,执行循环体,然后控制流返回到 for循环的头部。 如果迭代器为空了,则 for表达式执行完成。

for循环遍历数组内容的示例:

#![allow(unused)]
fn main() {
let v = &["apples", "cake", "coffee"];

for text in v {
    println!("I like {}.", text);
}
}

for循环遍历一个整数序列的例子:

#![allow(unused)]
fn main() {
let mut sum = 0;
for n in 1..11 {
    sum += n;
}
assert_eq!(sum, 55);
}

for循环等价于如下这样包含了匹配(match)表达式loop表达式。

'label: for PATTERN in iter_expr {
    /* loop body */
}

等价于:

{
    let result = match IntoIterator::into_iter(iter_expr) {
        mut iter => 'label: loop {
            let mut next;
            match Iterator::next(&mut iter) {
                Option::Some(val) => next = val,
                Option::None => break,
            };
            let PATTERN = next;
            let () = { /* loop body */ };
        },
    };
    result
}

这里的 IntoIteratorIteratorOption 是标准库的程序项(standard library item),不是当前作用域中解析的的任何名称。 变量名 nextiterval 也仅用于表述需要,实际上它们不是用户可以输入的名称。

注意:上面代码里使用外层 matche 来确保 iter_expr 中的任何临时值在循环结束前不会被销毁。 next 先声明后赋值是因为这样能让编译器更准确地推断出类型。

Loop labels

循环标签

句法
LoopLabel :
   LIFETIME_OR_LABEL :

一个循环表达式可以选择设置一个标签。 这类标签被标记为循环表达式之前的生存期(标签),如 'foo: loop { break 'foo; }'bar: while false {}'humbug: for _ in 0..0 {}。 如果循环存在标签,则嵌套在该循环中的带此标签的 break表达式和 continue表达式可以退出此标签标记的循环层或将控制流返回至此标签标记的循环层的头部。 具体请参见后面的 break表达式continue表达式

循环标签遵循局部变量的卫生性和遮蔽规则。例如,下面代码将打印 "outer loop":

#![allow(unused)]
fn main() {
'a: loop {
    'a: loop {
        break 'a;
    }
    print!("outer loop");
    break 'a;
}
}

break expressions

break表达式

句法
BreakExpression :
   break LIFETIME_OR_LABEL? Expression?

当遇到 break 时,相关的循环体的执行将立即结束,例如:

#![allow(unused)]
fn main() {
let mut last = 0;
for x in 1..100 {
    if x > 12 {
        break;
    }
    last = x;
}
assert_eq!(last, 12);
}

break表达式通常与包含 break表达式的最内层 loopforwhile循环相关联,但是可以使用循环标签来指定受影响的循环层(此循环层必须是封闭该 break表达式的循环之一)。 例如:

#![allow(unused)]
fn main() {
'outer: loop {
    while true {
        break 'outer;
    }
}
}

break表达式只允许在循环体内使用,它有 breakbreak 'label 或(参见后面break EXPRbreak 'label EXPR 这四种形式。

Labelled block expressions

带标签的块表达式

句法
LabelBlockExpression :
   BlockExpression

带标签的块表达式同普通的块表达式完全相同,只是它允许在块中使用 break表达式。 跟循环不同,标签表达式中的 break表达式必须带有一个标签(即标签不是可选的)。 同时,带标签的块表达式必须以标签开头。

#![allow(unused)]
fn main() {
fn do_thing() {}
fn condition_not_met() -> bool { true }
fn do_next_thing() {}
fn do_last_thing() {}
let result = 'block: {
    do_thing();
    if condition_not_met() {
        break 'block 1;
    }
    do_next_thing();
    if condition_not_met() {
        break 'block 2;
    }
    do_last_thing();
    3
};
}

continue expressions

continue表达式

句法
ContinueExpression :
   continue LIFETIME_OR_LABEL?

当遇到 continue 时,相关的循环体的当前迭代将立即结束,并将控制流返回到循环头。 在 while循环的情况下,循环头是控制循环的条件表达式。 在 for循环的情况下,循环头是控制循环的调用表达式。

break 一样,continue 通常与最内层的循环相关联,但可以使用 continue 'label 来指定受影响的循环层。 continue表达式只允许在循环体内部使用。

break and loop values

breakloop返回值

当使用 loop循环时,可以使用 break表达式从循环中返回一个值,通过形如 break EXPRbreak 'label EXPR 来返回,其中 EXPR 是一个表达式,它的结果被从 loop循环中返回。 例如:

#![allow(unused)]
fn main() {
let (mut a, mut b) = (1, 1);
let result = loop {
    if b > 10 {
        break b;
    }
    let c = a + b;
    a = b;
    b = c;
};
// 斐波那契数列中第一个大于10的值:
assert_eq!(result, 13);
}

如果 loop 有关联的 break,则不认为该循环是发散的,并且 loop表达式的类型必须与每个 break表达式的类型兼容。 其后不跟表达式的 break 被认为与后跟 ()break表达式的效果相同。

1

求得 () 类型以外的值。