fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {result}");
}
According to the rust book, a statement ends with a semicolon, and they don't return any value. Only expressions return a value.
Quoting from the rust book Chapter 3.3 Statements and Expressions
Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, and it will then not return a value.
So here in their example, break counter * 2; ends with a semi colon. I don't see a compiler error. But it returns a value to result.
Just to verify, I removed the semicolon and it still works. In fact removing the semicolon makes more sense to me.
So why does adding the semicolon still return a value back to result? can someone help explain?
loop { … } is an expression. It evaluates to the value given to the break … expression. Evaluating the break … expression, even when turned into a statement like break …;, will make control-flow immediately leave the surrounding loop expression, and make that loop expression evaluate to the provided value, as explained before.
But doesn't loop{...}; also end with a semicolon? Wouldn't that make it a statement again?
Ok sorry. The semicolon is not that of loop but from the let statement.
By the way, the break counter * 2 expression itself never evaluates to anything anyways. In particular it doesn’t evaluate to counter * 2. It’s similar to return … expressions in that way. To the type system, it looks like the expression actually evaluates a value of the “never” type never - Rust, but that type does not have any values. It’s a neat type-system trick to allow fancy things like let x = if … { break } else { other_value() } (where other_value() is of some type Foo and you want to (conditionally) assign x (also of type Foo) to that value, or break execution from the surrounding loop).
No, the semicolon after the loop { … } expression is part of the let statement. Or maybe “terminates the let statement”, depending on who you ask, it’s not 100% clear whether or not the semicolon should count as “part of the statement”. The syntax of a let statement (without a type annotation, and with an initializer) goes let PATTERN = EXPRESSION; ending with a semicolon. A full description of the syntax of let and let-else can be found here in the reference: Statements - The Rust Reference
Okay I had tried to remove the break and just kept counter * 2, hoping it would return the evaluated answer back to result and break the loop. But that assumption was wrong, and it instead it gave a compilation error. Saying that it found an integer but expected ()
let result = loop {
counter += 1;
if counter == 10 { counter * 2}
I will have to go through the "never" type. Haven't really reached that section yet. So bottom line is that the break can act like a return statement, in certain statements like let
would mean the if counter == 10 { counter * 2 } evaluates to counter * 2 (an integer) in the true case, and nothing (of type ()) in the false case (due to a lack of else branch)`, so that’s at type-mismatch already. If you added an else branch, e.g.
In Rust, almost everything is an expression. Really, the only kind of statement that isn’t just an expression plus the (sometimes optional) semicolon is a let statement.
So, yes, it’s safe to say that break (or break …) is an expression; and every expression can become a statement (by adding the semicolon) so break; (or break …;) would be a statement.
As a good demonstration that break is an expression, something like
let x: String = break;
works. It also demonstrates the “magical” property of the never type to convert implicitly to any other type, which also powers todo!(), useful for writing code that isn’t quite done yet, but you already want to check whether or not it compiles.
That's not accurate. break breaks from the loop. It has nothing to do with the let; the let is a red herring. If you have a loop that contains a break, then the value of the loop is the expression specified after the break.
Right--you can have expressions that return a value, even if that return value is not assigned to anything.
For example:
fn main() {
let mut counter = 0;
loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
let result = counter * 2;
println!("The result is {result}");
}
Here, all I've done is remove the let statement from the output of the loop, and instead assigned to result in a separate statement. The loop/break still creates an expression that returns a value, technically; but nothing is done with that value, and it is lost.
(I don't know what the compiler actually does with this--if it realizes the value is meaningless and thus optimizes out the creation of the return value--but conceptually that is what happens)