Expressions and statements
In Rust, almost everything is an expression: a piece of code that produces a value. 3 + 4 is an expression, a function call is an expression, and so is an if with its branches. A whole block in curly braces is an expression too. It evaluates to its last expression, as long as that last expression has no semicolon.
let y = {
let a = 3;
a + 1 // no semicolon: this value flows out of the block
};
println!("{}", y); // 4
Inside the block, let a = 3; is a statement. It does some work and produces no value of its own. The final line a + 1 is an expression, and because nothing follows it, its value becomes the value of the entire block. Note that the variable a lives only inside the braces. It can't be used after the closing };.
The same rule applies to if, which is why Rust needs no ternary operator:
let size = 7;
let label = if size > 5 { "big" } else { "small" };
println!("{}", label); // big
Functions return their last expression
A function body is a block, so a function returns its last expression automatically. The keyword return is only needed for early exits from the middle of a function.
fn add_one(n: i32) -> i32 {
n + 1 // the return value: no `return`, no semicolon
}
The arrow -> i32 promises that the body produces an i32. The compiler then checks that the last expression really has that type.
The trap: one accidental semicolon
A semicolon turns an expression into a statement, and a statement's value is (), the empty "unit" type. If you write n * n; as the last line of a function that promises an i32, the function ends up returning (), and the compiler stops you:
error[E0308]: mismatched types
expected `i32`, found `()`
implicitly returns `()` as its body has no tail or `return` expression
help: remove this semicolon to return this value
The compiler even states the fix: remove the semicolon. The same thing happens with blocks. If the last line of a block ends with a semicolon, the block's value is ():
let unit = { let y = 5; y + 1; };
println!("{:?}", unit); // ()
Here the addition is computed and thrown away, and the compiler warns about the unused result.
The exercise, step by step
The exercise asks for a function square, a block bound to doubled, and two printed results:
doubled = 42
square(6) = 36
- Declare the helper.
fn square(n: i32) -> i32with its closing brace. The arrow commits the body to producing ani32, so the compiler can check the next step. - Write the body as a bare expression.
n * n, with noreturnand no semicolon. A trailing;would produce the mismatched types error above. - Open a
let doubled = { ... };block. The closing brace needs the;after it, because that semicolon ends theletstatement. - Declare a local inside the block.
let x = 21;ends with a semicolon, as everyletdoes. - End the block with a tail expression.
x * 2without a semicolon. Its value, 42, flows out intodoubled. - Print
doubled. The format string is"doubled = {}", and the{}placeholder is filled with the value after the comma. Heredoubledis a plaini32, which proves the block evaluated to a value. - Print the helper's result.
square(6)goes right inside the arguments. A call is an expression, so it can sit anywhere a value is expected, with no variable needed first.
fn square(n: i32) -> i32 {
n * n
}
let doubled = {
let x = 21;
x * 2
};
println!("doubled = {}", doubled);
println!("square(6) = {}", square(6));
Common mistakes
- A semicolon after the tail expression. It turns the value into
(). If you seeexpected i32, found (), look for a semicolon on the line that should produce the value. - Forgetting the semicolon after the closing brace of a
let. The statementlet doubled = { ... }needs it. - Using a block's local variable outside it.
xis gone after the closing brace. - Using
returninside a block expression by habit. It leaves the whole function, not just the block.
Key takeaways
- A block evaluates to its last expression when that expression has no semicolon.
- A function returns its last expression, so
returnis for early exits. - Adding a semicolon to a would-be value turns it into a statement worth
(), which is the cause of the mismatched types error.
