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
}