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

Ale*_*vik 21 function panic rust

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

  • 当一个panic!函数
  • 当函数中有无限循环时

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

fn func() -> ! {
    panic!("Error!");
}
Run Code Online (Sandbox Code Playgroud)

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

fn func() {
    panic!("Error!");
}
Run Code Online (Sandbox Code Playgroud)

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

Cer*_*rus 37

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

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

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

func声明为 return时!,该函数编译成功。如果我们删除返回类型,func将被声明为 returned (),并且编译会中断

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

您还可以将其与 的定义Result::unwrap进行比较:

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

这里,unwrap_failed是returning!,所以它与万一返回的任何类型统一Ok

  • 您试图做相反的事情 - 将 `i32` (默认整数类型)强制转换为 `!`。这显然行不通。这里的“兼容性”是单向的——“!”类型的“值”(根据定义不存在)可以在任何需要任何其他类型的地方使用,而不是相反。 (11认同)
  • @AleksandrNovik:这与动态类型无关。`!` 是一个*底层类型*,而不是动态类型,即它是所有其他类型的子类型的类型。 (6认同)
  • 它不像动态类型那样工作的原因是“!” 是一个函数永远不会返回的承诺。因此,我们可以假装它会返回我们想要的任何类型;这永远不会造成问题,因为它永远不会返回。这就是为什么你的例子不被允许的原因。因为它实际上不是动态类型。 (5认同)
  • 在我看来,“!”让我们可以实现动态类型之类的东西。但与此同时,这不起作用: `fn func() -> ! { 123 }`,虽然你这么说__!可以强制转换为任何其他类型,因此与任何其他类型兼容__ (2认同)

Fra*_*gné 14

编译器知道发散表达式(求值顺序)后面的任何内容都是无法访问的。在决定局部变量是否初始化时,它可以使用此信息来避免误报。

考虑以下示例:

use rand; // 0.8.4

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

fn diverge() {
    panic!("Crash!");
}
Run Code Online (Sandbox Code Playgroud)

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

use rand; // 0.8.4

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

fn diverge() {
    panic!("Crash!");
}
Run Code Online (Sandbox Code Playgroud)

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

fn diverge() -> ! {
    panic!("Crash!");
}
Run Code Online (Sandbox Code Playgroud)

然后代码编译成功。编译器知道,如果else采用分支,它永远不会到达,println!因为diverge()存在分歧。else因此,分支未初始化并不是错误foo