The following does not compile, as there is a type mismatch - the if-block is required to return unit type (), and the value that the if-expression evaluates to is bool.
However, if I add the return statement to the if-block like this
return true,
it does compile. I am confused now. Did the return keyword trigger some kind of a side-effect that prematurely returned from the function? How did it circumvent the requirement on the if-block to evaluate to unit type?
Hmmm, my compiler says the following. expected (), found bool note: if expressions without else evaluate to ()
So it is not an expression, but the compiler still wants me to not return bool from it? Confused with this one.
Yes, statements do not have a value, and not having a value means the same thing as having a value of (). I'm this case, rust is catching a real bug in your code.
A nicer solution than adding a return would be to add an else. But for this particular function a nicer approach yet would be to remove the if and have
fn foo() -> bool {
bar() == 1
}
But I'm guessing your actual function is more complicated.
That's correct. You cannot "return" anything from a lonely if because that value would be lost.
The value is not returned from the function, but only from the block.
The following is valid code:
let x = if (condition) { 1 } else { 0 };
While this is not:
let x = if (condition) { 1 };
What would be the value of x if condition is false?
So if, [else if] must be followed by else to make a valid expression? Aha, so your second example is equivalent to my non-working code and they both give E0317 error, so the compiler basically told me about the fact that I cannot return from a lonely if-expression without using the return statement.
In other words, this is what @droundy said about return statement having type ()?
Well, I cannot always construct valid if-else blocks, as sometimes I need to optionally return on some condition. In such cases, I must use return, right (there is no other option)? Previously I tried to avoid writing return because AFAIK it is considered bad style.
In fact, return and others (break, std::process::exit(), panic!(), unreachable!(), and so on) have ! type (never type) and can be treated as any types as necessary.
Such expressions are called "diverging".
#[allow(unreachable_code)]
#[allow(unused_variables)]
#[allow(dead_code)]
fn foo() -> i32 {
// `return 0` is `!` type, so it can be treated as `i32`.
let ret: i32 = return 0;
// `return 0` is `!` type, so it can be also treated as `String`.
let ret2: String = return 42;
// `todo!()` is `!` type (because it returns any value at runtime),
// so it can be treated as `&str`.
let unr: &str = todo!();
// `todo!()` is `!` type, and can be also treated as `i32`.
let unr2: i32 = todo!();
// `std::process::exit()`, `panic!()`, `break`, and more friends!
}
You can consider if cond { then } as if cond { then } else { () }, and this makes it clear that then part and if itself is also ().
fn foo() -> i32 {
// `if` without `else` itself is `()`.
let _: () = if true {
// `if` without `else` requires then-clause to be `()` type.
()
};
let _: () = if true {
// `return` can be any type, and `if` without `else`
// requires expression here to be `()`,
// so `return 42` in this case is treated as `()`.
return 42
};
let _: () = if true {
// Block `{ return 42; }` is "diverging" (i.e. does not return
// any value at runtime), because `return 42;` is
// diverging. This allows `{ return 42; }` to be treated as
// any type (including `()`).
return 42;
};
0
}