While semicolons often make a difference in Rust, this is a case where it turns out to work either way, with the same behavior. Stylistically, I’d usually prefer including the semicolon here… in face, turns out rustfmt
will even add it for you here!
Blocks in Rust are usually of the form
{
first_statement;
second_statement;
…
last_statement;
final_expression
}
Blocks are expressions, and evaluate to the value of the final expression.
So if you do
let x = {
some_statement;
an_expression
}
then some_statement
will be executed; afterwards an_expression
is evaluated, finally, the result of that becomes the value of the whole block and gets assigned to x
.
If a block doesn’t need a resulting value, it can be the “unit” value instead, in Rust syntax the value ()
of type ()
a block of the type
{
first_statement;
second_statement;
…
last_statement;
// no final expression here
}
is usually equivalent to
{
first_statement;
second_statement;
…
last_statement;
() // unit-value final expression here
}
The full details on how blocks work are a bit more complex (the so-called “never type” gets involved if the block always “diverges”), but this doesn’t matter here.
An if
expression can have 2 blocks with else
if condition {
// block 1
} else {
// block 2
}
As block are expressions and evaluate to things, if
expressions can do the same.
An if
expression without else
if condition {
// block 1
}
is equivalent to an empty else
block
if condition {
// block 1
} else {
}
which is also equivalent, as discussed above, to a block with unit-value final expression:
if condition {
// block 1
} else {
()
}
It’s this consistency that motivates why if
expressions can have final values in the first place. As long as the final value has type “()
”. For consistency, it’s allowed then. It is however rarely useful. Hence the stylistic suggestion to usually put the semicolon there rather than not.
To explain all of the syntactical considerations that make this code…
loop {
counter+=1;
if counter==10 {
break counter*2
}
};
…work, we would also need to talk about why the if
expression doesn’t need a semicolon at the end of the loop
body; really it’s the same story – consistency
For consistency with all the other usages of blocks in Rust, even loop
bodies support a final expression, as long as it’s of type ()
. And if there were further statements after the if
, we could even talk about the special semicolon rules that allow certain statements like an if
-expression statement, to stay semicolon-free.
So, if you look at it in full detail, Rust syntax is somewhat complex, after all — fortunately, the type system makes sure that it’s almost impossible to have this complexity result in a bug in you program, anyway; you’ll usually get a compilation error that advises you to add or remove a semicolon as necessary.
And then we would also get the the value of break
expressions the effect of evaluating such an expression is clear, it breaks the loop and makes the surrounding loop
expression evaluate so something. But inside of the loop body, break …
also was an expression. That’s what some comments above already explored; it’s another time where the “never type” is technically involved; for the context of if
expressions however, all that matters is that the break …
itself is considered an expression that can evaluate to ()
(after a coercion), which is why it is allowed here without the semicolon in the first place, just like if you were to put something like ()
there directly and unlike the compiler error you get when you put a value of different type.