带break的循环表达式的返回值

Web*_*ode 11 break rust

实施例1

fn five() -> i32 {
    5   // ; not allowed I understand why
}

fn main() {
    let x = five();
    println!("The value of x is: {x}");
}
Run Code Online (Sandbox Code Playgroud)

示例 2(来自https://doc.rust-lang.org/stable/book/ch03-05-control-flow.html

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };
    println!("The result is {result}");
}
Run Code Online (Sandbox Code Playgroud)

我明白为什么在示例 1 中它必须是5而不是5;,但我对示例 2 感到困惑,并且有几个问题。

问题一:

为什么我们这里有;?没有它也能工作;,那么为什么它在那里呢?这是一些 Rust 约定还是有一些技术原因?

问题2:

如果我这样做,break; counter * 2;它不会返回值。break; counter * 2;和 和有什么区别break counter * 2;
为什么第二个有效?

问题3:

如果我做:

break counter * 2
println!("After break");
Run Code Online (Sandbox Code Playgroud)

编译错误是:错误:预期;,发现println
如果我这样做:

break counter * 2;
println!("After break");
Run Code Online (Sandbox Code Playgroud)

不再有编译错误,但是:

15 |             println!("After break");
   |             ^^^^^^^^^^^^^^^^^^^^^^^ unreachable statement
Run Code Online (Sandbox Code Playgroud)

但至少我明白这一点。
我不明白的是为什么它 break counter * 2工作正常,但如果我在它后面添加一些东西,我们就会出现编译错误。

老实说,我对示例 2 感到困惑,我的理解是,如果我们想从表达式返回值,最后一行应该没有“;” (如示例 1),但示例 2 显然证明了事实并非如此。

Sil*_*olo 19

Rust 是一种非常面向表达的语言。至关重要的是,表达式返回值。当你编写一个函数时,你就是在编写一个表达式。该表达式可以由多个用分号分隔的语句组成。

这就是 Rust 与大多数其他 C 派生语言的不同之处。表达式是 Rust 中的驱动程序,分号分隔语句。所以一个有效的表达式是{ a ; b ; c ; d },其中d是最终结果。ab、 和c只是副作用。相比之下,在 C 中,函数是由分号终止的语句序列,并且语句包含表达式。因此,在 C 中,函数体可能看起来像{ a ; b ; c ; d ; },其中执行每个语句都是为了产生副作用,其中一个可能碰巧是一个return语句,但它仍然是一个语句。

如果 Rust 中的表达式序列分号结尾,Rust 会假设您打算插入一个额外的字符()作为结尾,因此{ a ; b ; c ; d ; }会转换为{ a ; b ; c ; d ; () }. 这就是为什么我们不必()在所有返回单元的函数的末尾编写。这只是默认值。

这是一种更实用的看待事物的方式。函数返回一个值,无论发生什么都是副作用。函数末尾的“通常”返回值就是该值,作为表达式。

现在,因为 Rust 支持更命令式的风格(并且因为它通常有用且方便),Rust 还支持诸如breakand之类的语句return,这些语句早期就打破了通常的控制流。这些是陈述。它们对返回值有副作用,但它们不是表达式的“通常”返回值。

let result = loop {
  counter += 1;
  if counter == 10 {
    break counter * 2;
  }
};
Run Code Online (Sandbox Code Playgroud)

loop像 Rust 中的大多数东西一样,的内部是一个表达式。所以它可以返回一个值。该值将被忽略,因为循环将再次运行。在这种情况下,该块相当于

let result = loop {
  counter += 1;
  if counter == 10 {
    break counter * 2;
    ()
  } else {
    () // 'if' can also insert () in the else block when used as an expression
  }
};
Run Code Online (Sandbox Code Playgroud)

我们()明确返回。如果删除分号,您将得到

let result = loop {
  counter += 1;
  if counter == 10 {
    break counter * 2
  } else {
    () // 'if' can also insert () in the else block when used as an expression
  }
};
Run Code Online (Sandbox Code Playgroud)

break作为表达式,也“返回”一个值。该值属于发散类型,称为neveror!。由于break保证会发散(即退出通常的控制流),因此它返回!,这是Rust 中唯一与所有其他类型兼容的类型。所以这个表达式的结果if仍然是(),因为!可以转换为()。当然,这都是没有意义的,因为loop如果没有被破坏,它将再次运行,但这就是 Rust 内部的推理。

总之,您不会尝试从{ ... }循环块的最后一行返回。你试图跳出循环,这不是正常的返回;它打破了循环遵循的通常规则,因此它需要一个特殊的语句,并且Rust 中的语句之间用分号分隔。事实上,您可以在没有分号的情况下结束语句序列,这是偶然的,因为loop无论如何都会忽略其块的结果。