How to statically assert the end of a function is unreachable

*爱你&永不变心* 提交于 2021-02-11 18:01:34

问题


I have a fairly complicated match statement (with nested ifs and the like) at the end of a function. Every branch of this should either explicitly return from the function, or call some -> ! function (such as process::exit).

For both communication to other programmers, and to protect myself from myself, I'd like to tell the compiler to assert that anything after this match is unreachable. I know it knows how to do this statically, since if I put code there I get compile-time warnings.

Two things I've tried:

  1. Assign the match statement to let _: ! = match .... However, the ! is still experimental, so this doesn't work

  2. Wrap the match in a closure move || -> ! { match ... }();. However, this restricts me from being able to just return from the parent function.


Specific details of my situation which don't necessarily apply generally:

  • Function in question is fn main() -> ()
  • Conditional logic must either diverge to a ()-returning function, or diverge to a !-returning function
  • Failure to do so indicates a path where an error is either not correctly handled or reported
  • return-ing functions from within the conditional logic need to be there to make use of values unwraped by matches

回答1:


This only seems to become a problem because of a few exceptional quirks around the unit type ():

  1. () is the default when the return type is omitted from a function signature (so fn main() is equivalent to fn main() -> ());
  2. And even if you do not provide any expression to return, empty blocks or statements in code also evaluate to ().

The example below works because the semi-colon turns the expression 5 into a statement, the value of which is hence discarded.

fn foo() {
    5;
}

Recursively, it is easy for all match arms to evaluate to () when none of them yield an outcome of another type. This is the case when using return because the return statement creates a true divergence from the execution flow: it evaluates to the never type !, which 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
        }
    }
}

The ubiquity of this unit type generally contributes to elegant code. In this case however, it may trigger a false positive when a more strict control flow is intended. There is no way for the compiler to resolve this unless we introduce another type to counter it.

Therefore, these solutions are possible:

  1. Use a different return type for the function. If there is nothing applicable to return (e.g. only side-effects), you can use nearly any type, but another unit type provides a better assurance that it becomes a zero-cost abstraction.

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. Do not use return or break statements, and establish that all of your match arms need to evaluate to something other than ().

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. The reverse: establish that the match expression evaluates to a zero type (one like the never type !), so that no match arm can return from it except using control flow statements such as break or return.

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 ()
        }
    };
}

See also:

  • Why does Rust not have a return value in the main function, and how to return a value anyway?


来源:https://stackoverflow.com/questions/62405398/how-to-statically-assert-the-end-of-a-function-is-unreachable

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!