【问题标题】:How to statically assert the end of a function is unreachable如何静态断言函数的结束是不可达的
【发布时间】:2020-10-05 21:05:38
【问题描述】:

我在函数末尾有一个相当复杂的match 语句(带有嵌套的ifs 等)。它的每个分支都应该从函数中显式返回,或者调用一些-> ! 函数(例如process::exit)。

为了与其他程序员的交流,以及保护自己免受自己的伤害,我想告诉编译器断言match 之后的任何内容都无法访问。我知道它知道如何静态地执行此操作,因为如果我将代码放在那里,我会收到编译时警告。

我尝试过的两件事:

  1. match 语句分配给let _: ! = match ...。但是,! 仍处于试验阶段,所以这不起作用

  2. match 包裹在闭包move || -> ! { match ... }(); 中。但是,这限制了我只能从父函数return


我的情况的具体细节不一定普遍适用:

  • 有问题的函数是fn main() -> ()
  • 条件逻辑必须要么发散到 ()-returning 函数,要么发散到 !-returning 函数
  • 不这样做表示错误未正确处理或报告的路径
  • return-ing 条件逻辑中的函数需要在那里使用匹配解包的值

【问题讨论】:

  • “我知道它知道如何静态地执行此操作,因为如果我将代码放在那里,我会收到编译时警告。”所以只要把警告变成错误就完成了吗?
  • 我不知道静态是否可行,但对于文档方面和运行时检查,您始终可以在匹配后添加unreachable!()
  • 提供一个具体的最小例子来说明这里想要改进的地方真的很有帮助。函数的返回类型是什么?考虑到match 表达式是函数的最后一个表达式,为什么不将函数的末尾留空,并使用此匹配的结果作为其最终输出呢?匹配表达式已经被要求是详尽的。
  • 我不太明白问题的重点,it's already an error to forget to return in rust
  • @trentcl 抱歉,我可能说错了(我不一定知道这些术语定义的所有角落)。我只是指包含return foo(); 的路径,其中foo() -> () 并且因此match 语句将永远不会完成(由于return)——我的意思是“分歧”。

标签: rust


【解决方案1】:

这似乎只是因为单元类型 () 周围的一些异常怪癖而成为一个问题:

  1. () 是函数签名中省略返回类型时的默认值(因此fn main() 等价于fn main() -> ());
  2. 即使您不提供任何要返回的表达式,代码中的空blocksstatements 也会计算为()

下面的例子是有效的,因为分号将表达式5 变成了一个语句,其值因此被丢弃。

fn foo() {
    5;
}

当所有匹配臂都没有产生另一种类型的结果时,递归地很容易评估为()。使用return 时会出现这种情况,因为return 语句与执行流产生了真正的分歧:它评估为永不类型!,即coerces to any other type

fn foo(bar: i32) {
    match bar {
        1 => {
            return do_good_things(); // coerces to () because of the default match arm
        }
        0 => {
            return do_other_things(); // coerces to () because of the default match arm
        }
        _ => {
            // arm evaluates to (), oops
        }
    }
}

这种单元类型的普遍性通常有助于编写优雅的代码。然而,在这种情况下,当需要更严格的控制流程时,它可能会触发误报。编译器无法解决这个问题除非我们引入另一种类型来应对它

因此,这些解决方案是可能的:

  1. 为函数使用不同的返回类型。如果没有适用于 return 的东西(例如只有副作用),您几乎可以使用任何类型,但另一种单元类型可以更好地保证它成为零成本抽象。

Playground

struct Check;

fn foo(bar: i32) -> Check {
    match bar {
        1 => {
            do_good_things();
            Check
        }
        0 => {
            do_other_things();
            return Check; // can use return
        }
        _ => {
            // error[E0308]: expected struct Check, found ()
        }
    }
}
  1. 不要使用returnbreak 语句,并确定所有匹配臂都需要评估为() 以外的值。

Playground

struct Check;

fn foo(bar: i32) {
    let _: Check = match bar {
        1 => {
            do_good_things();
            Check
        }
        0 => {
            do_other_things();
            Check
        }
        _ => {
            // error[E0308]: expected struct Check, found ()
        }
    };
}
  1. 反过来:确定 match 表达式的计算结果为零类型(类似于 never 类型 !),因此除了使用诸如 breakreturn 之类的控制流语句之外,没有匹配分支可以从它返回.

Playground

enum Nope {}

fn foo(bar: i32) {
    let _: Nope = match bar {
        1 => {
            return do_good_things();
        }
        0 => {
            return do_other_things();
        }
        _ => {
            // error[E0308]: expected enum `Nope`, found ()
        }
    };
}

另见:

【讨论】:

  • type Check = Result<(), Never>; enum Never {} 使第一个选项可以与main 一起使用(它只能返回实现Terminate 的东西)。当然你必须在match中使用Ok(())而不是Check
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-29
  • 2016-12-29
  • 1970-01-01
  • 2011-10-20
  • 1970-01-01
  • 2022-09-28
相关资源
最近更新 更多