In Rust, almost everything is an expression. This means that almost everything returns a value. One exception is the variable binding statement let. In a let statement, and many others, the ending semicolon is a mandatory part of the syntax.
However, in expressions, the semicolon has a double role: it throws away a return value of the expression in addition to allowing further expressions. So if the expression is the last in a block, having a semicolon there means that the last value is thrown away, and not having a semicolon there means to return the last value.
An example should make it clear:
// 04/semicolon_block/src/main.rs
fn main() { let x = 5u32; let y = { let x_squared = x * x; let x_cube = x_squared * x; x_cube + x_squared + x }; let z = { 2 * x; }; println!("x is {:?}", x); println!("y is {:?}", y); println!("z is {:?}", z); }
We have two different uses of the semicolon. Let's look at the let y line first:
let y = { let x_squared = x * x; let x_cube = x_squared * x; x_cube + x_squared + x // no semi-colon };
This code does the following:
- The code within the braces is processed
- The final line, without the semicolon, is assigned to y
Essentially, this is considered as an inline function that returns the line without the semicolon into the variable.
The second line to consider is for z:
let z = { 2 * x; };
Again, the code within the braces is evaluated. In this case, the line ends with a semicolon, so the result is thrown away and the empty value () gets bound to z.
When it is executed, we will get the following results:
In the code example, the line within fn main calling recurse gives the same result with or without the semicolon, because the Rust runtime doesn't use main's return value for anything.