Rust 中的发散函数有什么意义?

What is the point of diverging functions in Rust?

我已经阅读了几个关于 SO 的答案,并收集了这些用例:

但我仍然不清楚为什么我们需要这样定义函数:

fn func() -> ! {
    panic!("Error!");
}

如果它的工作方式与此相同(没有感叹号):

fn func() {
    panic!("Error!");
}

同时,为什么我们需要在无限循环的函数中使用!?看起来这个签名没有带来任何真实的使用信息。

这些签名之间的主要区别归结为 ! 可以 强制 为任何其他类型,因此 兼容 与任何其他类型(由于从未采用此代码路径,我们可以假设它是我们需要的任何类型)。当我们有多个可能的代码路径时,这一点很重要,例如 if-elsematch.

例如,考虑以下(可能是人为设计的,但希望足够清楚)代码:

fn assert_positive(v: i32) -> u32 {
    match v.try_into() {
        Ok(v) => v,
        Err(_) => func(),
    }
}

func声明为return!时,这个函数compiles successfully. If we drop the return type, func will be declared as returning (), and the compilation breaks:

error[E0308]: `match` arms have incompatible types
 --> src/main.rs:8:19
  |
6 | /     match v.try_into() {
7 | |         Ok(v) => v,
  | |                  - this is found to be of type `u32`
8 | |         Err(_) => func(),
  | |                   ^^^^^^ expected `u32`, found `()`
9 | |     }
  | |_____- `match` arms have incompatible types

您也可以将此与 definition for Result::unwrap 进行比较:

pub fn unwrap(self) -> T {
    match self {
        Ok(t) => t,
        Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
    }
}

在这里,unwrap_failed is returning !,因此它与 returned 在 Ok 情况下的任何类型统一。

编译器知道发散表达式(w.r.t.求值顺序)后面的任何东西都是不可到达的。在决定是否初始化局部变量时,它可以使用此信息来避免漏报。

考虑以下示例:

use rand; // 0.8.4

fn main() {
    let foo;
    if rand::random::<bool>() {
        foo = "Hello, world!";
    } else {
        diverge();
    }
    println!("{foo}");
}

fn diverge() {
    panic!("Crash!");
}

我们声明了一个变量foo,但是我们只在if表达式的一个分支中初始化它。编译失败并出现以下错误:

error[E0381]: borrow of possibly-uninitialized variable: `foo`
  --> src/main.rs:10:15
   |
10 |     println!("{foo}");
   |               ^^^^^ use of possibly-uninitialized `foo`

但是,如果我们像这样更改 diverge 函数的定义:

fn diverge() -> ! {
    panic!("Crash!");
}

然后代码成功编译。编译器知道如果 else 分支被采用,它永远不会到达 println! 因为 diverge() 发散。因此,else 分支未初始化 foo.

并不是错误