【问题标题】:What is the point of diverging functions in Rust?Rust 中不同功能的意义何在?
【发布时间】:2022-01-22 12:58:02
【问题描述】:

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

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

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

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

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

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

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

【问题讨论】:

    标签: function rust panic


    【解决方案1】:

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

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

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

    func被声明返回!时,这个函数compiles成功。如果我们去掉返回类型,func会被声明为返回(),编译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 !,所以它与Ok case 中返回的任何类型相统一。

    【讨论】:

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

    编译器知道任何遵循发散表达式(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 并不是错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-02
      • 1970-01-01
      • 1970-01-01
      • 2020-03-13
      • 2023-01-18
      • 1970-01-01
      • 2020-05-02
      • 1970-01-01
      相关资源
      最近更新 更多