Evaluation of expressions
Expressions with a scope (curly brackets) evaluate to the last expression inside it. That includes simple scopes, functions, but also if
and match
statements.
This also explains the meaning of the semicolon as the statement terminator. It is used to terminate assignments, but also converts expressions into statements, converting the evaluated type to the unit
type. There is one exception though, as the never
type isn't converted by the terminator.
Now why would you want it to work that way? Well firstly, it's still relatively easy to find the return expression without the return
keyword, as it's always the last expression inside a scope and therefore is at the bottom of it.
On the upside, it heavily promotes clear control flow, as this only works when the control flow actually returns at the bottom. When you encounter a return
keyword, it's a signal to the programmer that it's an early return, so you should exercise more awareness and caution.
Speaking of which, the return
statement is special in that it evaluates into the never
type inside a function, so anything coming after it will not be executed and have no effect on the program.
I did say that if
and match
statements have this behaviour, too. They evaluate to the last statement in the taken arm. To enable this, they need each arm to evaluate to the same type, of course. An if
statement needs an else
block, so there's always a return value.
Funnily enough, because a whole block evaluates to a single value, they can be used anywhere where expressions are allowed. This includes field and variable assignments.
Now of course, we want to know how that looks in practice. Have at it:
// Return the result of the if statement
fn example1(condition: bool) -> usize {
if condition {
42
} else {
69
}
}
fn example2(condition: bool) -> usize {
let return_value = if condition {
42
} else {
69
};
// Just return the variable
return_value
}