Why does Rust allow code with the wrong return type, but only with a trailing semicolon?
Asked Answered
B

2

8

Consider the following Rust code:

fn f() -> i32 {
    loop {
        println!("Infinite loop!");
    }
    println!("Unreachable");
}

This compiles (with a warning) and runs, despite the fact that the return type is wrong. It would seem that the compiler is OK with the return type of () in the last line because it detects that this code is unreachable.

However, if we remove the last semicolon:

fn f() -> i32 {
    loop {
        println!("Infinite loop!");
    }
    println!("Unreachable")
}

Then the code no longer compiles, giving a type error:

error[E0308]: mismatched types
  --> src/main.rs:14:5
   |
14 |     println!("Unreachable")
   |     ^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found `()`
   |
   = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

Why is this? Isn't the return type the same, (), in both of these code snipets?


Note: I'm interested in understanding why the Rust compiler behaves differently on these two examples, i.e. how the Rust compiler is implemented. I did not mean to ask a philosophical question about how it "should" behave, from the perspective of language design (I understand that such a question would probably be off-topic).

Berberine answered 21/2, 2020 at 1:19 Comment(7)
The Rust compiler needs to infer a type for the function body. In the first case, there is no return expression, and apparently the compiler infers ! as the return type because of the infinite loop, which makes sense. In the second case, there's a return expression, so the type inference solver uses that to infer the type, which also makes sense. I don't think this is specified in the language reference, nor do I think it matters in any way – just omit the unreachable statement and you'll be fine.Dowd
@SvenMarnach It's still a question about the language. I think that is still on-topic.Ingrowth
@6005 Sven's comments are completely unrelated to the "toxic culture" that SO has been accused of (unless you actually think he is treating you differently because of your gender, sexuality, race etc). He contributed to a (civilised) discussion about whether or not your question is suitable for SO.Ingrowth
Maybe I was a bit terse, but I certainly didn't mean to offend. I don't think there is anything wrong with your question – I even enjoyed it. :) I just don't think there is any "right" answer to it, since the details of type inference in Rust are not specified. We can see what type inference does in this case, but there simply isn't any deeper reason to it. Maybe someone with intimate knowledge of compiler internals could explain why the compiler behaves this way, but we wouldn't learn much about the language.Dowd
@SvenMarnach Thanks for clarifying. It is just that what I was asking was why the compiler does this, nothing more :) So your first comment fully answers the question. I wasn't looking for some justification for why Rust does it that way (if none exists). In fact, if you turn your comments into an answer, I think that would be a good answerBerberine
Apparently I read too much into the "why" part of your question. My comment just describes whtat the compiler does, but it doesn't say why.Dowd
Note that you can get similar behavior by writing return <something>; and then using an automatic return at the end of the function. It looks like the compiler checks things in a way that allows it to see both and register it as an error even though it is unreachable.Salesclerk
D
7

The return type in the first code block is actually ! (called never) because you have a loop that never exits (so rust gives you a warning saying it's unreachable). The full type would be:

fn f() -> !

I suspect ! is more like the 'bottom' type in Rust than anything else. In the second case, your function likely errors out in an earlier stage during type checking because of the mismatch between i32 and () before the compiler gets to the 'unreachability' analysis, like it does in the first example.

edit: as suggested, here is the relevant part of the rust book https://doc.rust-lang.org/book/ch19-04-advanced-types.html#the-never-type-that-never-returns

Doall answered 21/2, 2020 at 2:51 Comment(4)
It might help to link to the section about it in the Rust book. Specifically, it states: "The formal way of describing this behavior is that expressions of type ! can be coerced into any other type."Salesclerk
Thanks! That is interesting, I will read into this. However, the warning is not related to that: the warning is just "unreachable statement" (pointing to the last println!)Berberine
That is exactly what I was alluding to. The statement is unreachable, and your return type is !.Doall
Thanks for your answer, I hope it is correct and I am not accepting a wrong explanation :)Berberine
B
0

(Converting Sven's first comment into an answer)

The Rust compiler needs to infer a type for the function body. In the first case, there is no return expression, and apparently the compiler infers ! as the return type because of the infinite loop, which makes sense. In the second case, there's a return expression, so the type inference solver uses that to infer the type, which also makes sense.

I don't think this is specified in the language reference, nor do I think it matters in any way – just omit the unreachable statement and you'll be fine.

Berberine answered 21/2, 2020 at 1:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.