Fancy 'foo(return x)' syntax: bug or feature?

Today I accidentally wrote the following code:

some_result.or(return Err(...))

And surprisingly it compiles with a warning and works. Then I created a snippet to check:

fn foo(_: i32) -> i32 {
    foo(return 42);
    0
}

fn main() {
    println!("{:?}", foo(0));
}

Playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=eeb2655c0b739b88a0e9f86d1d8b64d2

And it also works and prints "42". The warning also may confuse because it points at the same line where return located instead of the next one.

In any other language, this code will lead to a compilation error. I cannot find any reason why Rust compiler allows this.

Can you clarify it as the compiler bug or as something useful what is added intentionally?

return ... is in and on itself an expression, which evaluates to the diverging / never type !, as most unreachable expressions do. It thus typechecks with the input of foo (! can be coerced to any type); that's why the compilation goes "without a warning" except for the unreachable_code that follows.

foo(return 42)

This behavior is required since it's the same as

foo(if true {
    return 42
} else {
    0
})

which enables more complex conditions:

foo(if v.is_empty().not() {
    v.len()
} else {
    return 42;
})

etc.

The main offender here, imho, are the non-lazy or methods. Many people get them wrong, because their name seems to suggest the opposite (i.e., that they are lazy). That's why or_else is to be preferred, and tools like clippy will lint against usage* of .or() so I guess / hope it would catch your initial error.

*with a non trivial expression in or; things like .or(0) are accepted, but they are still not better than .or_else(|| 0), since such cases will be inlined anyways.

Note that or_else(|| return 42) is the same as .or_else(|| 42), since a return refers to the innermost function / closure. That's why in some cases you have no other choice but to perform a match or an if let explicitely.

3 Likes