Statements and Expressions
A statement is an instruction that performs an action and does not produce a value. An expression is code that evaluates to a value. Rust is an expression-based language: most constructs, including blocks, if, match, and loops, are expressions.
fn main() {
let x = 5;
let y = {
let a = 3;
a + 1
};
let z = if x > 3 { "big" } else { "small" };
println!("{x} {y} {z}");
}
5 4 big
Each let line is a statement. The right-hand sides 5, { ... }, and if ... else ... are expressions, and each one produces the value that the let stores.
Statements
Rust has four kinds of statements.
| Kind | Example | Purpose |
|---|---|---|
| Let statement | let a = 5; |
Introduces a variable binding |
| Item statement | fn helper() {}, struct P;, use std::fmt; |
Declares an item: function, type, constant, import |
| Expression statement | foo();, if x { ... } |
Evaluates an expression for its effect and discards the value |
| Macro statement | println!("hi"); |
Invokes a macro in statement position |
fn square(n: i32) -> i32 {
n * n
}
fn main() {
// Item statement: a function defined inside another function.
fn double(n: i32) -> i32 {
n * 2
}
// Let statements.
let a = square(4);
let b = double(a);
// Expression statements.
println!("a = {a}, b = {b}");
if b > 20 {
println!("b is large");
}
}
a = 16, b = 32
b is large
An item declared inside a function is visible only inside the block that contains it.
A let Statement Is Not an Expression
let produces no value, so it cannot appear where a value is expected. This differs from C and Ruby, where int a = b = 6; is valid.
// ❌ does not compile
fn main() {
let a = let b = 6;
}
error: expected expression, found `let` statement
--> a3.rs:3:13
|
3 | let a = let b = 6;
| ^^^
|
= note: only supported directly in conditions of `if` and `while` expressions
The note refers to if let and while let, where let is part of a condition. That use is a pattern-matching condition, not a let statement.
Expressions
An expression evaluates to a value. Almost everything that is not a let statement or an item is an expression.
| Expression | Example | Value |
|---|---|---|
| Literal | 42, "text", true |
The literal itself |
| Variable | x |
The value stored in x |
| Operator | a + b, !flag, x > 1 |
The result of the operation |
| Function call | square(4) |
The returned value |
| Method call | v.len() |
The returned value |
| Macro call | vec![1, 2], format!("{n}") |
The value the macro expands to |
| Block | { let a = 1; a + 1 } |
The value of its last expression |
if / else |
if c { 1 } else { 2 } |
The value of the branch taken |
match |
match n { 0 => "zero", _ => "other" } |
The value of the arm taken |
loop |
loop { break 5; } |
The value given to break |
| Closure | |a, b| a + b |
A closure value |
| Range | 0..10 |
A range value |
| Assignment | x = 5 |
() |
Expressions nest. Every part of a larger expression is itself an expression:
fn main() {
let total = {
println!("computing");
let base = 10;
base * 2
};
println!("total = {total}");
let v = vec![1, 2, 3];
let len = v.len();
let doubled: Vec<i32> = v.iter().map(|n| n * 2).collect();
let closure_result = (|a: i32, b: i32| a + b)(2, 3);
println!("{len} {doubled:?} {closure_result}");
}
computing
total = 20
3 [2, 4, 6] 5
Control Flow as Expressions
if, match, and loop produce values, so no separate ternary operator is needed.
fn main() {
let n = 7;
let kind = if n % 2 == 0 { "even" } else { "odd" };
let size = match n {
0 => "zero",
1..=9 => "small",
_ => "large",
};
let mut counter = 0;
let first_over_ten = loop {
counter += 3;
if counter > 10 {
break counter;
}
};
let label = {
let prefix = "n";
format!("{prefix}={n}")
};
println!("{kind} {size} {first_over_ten} {label}");
}
odd small 12 n=7
All branches of an if or match used as a value must have the same type:
// ❌ does not compile
fn main() {
let n = 3;
let label = if n > 2 { "big" } else { 0 };
}
error[E0308]: `if` and `else` have incompatible types
--> b8.rs:4:43
|
4 | let label = if n > 2 { "big" } else { 0 };
| ----- ^ expected `&str`, found integer
| |
| expected because of this
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0308`.
An if without an else evaluates to () when the condition is false, so it cannot produce any other type:
// ❌ does not compile
fn main() {
let n = 3;
let label = if n > 2 { "big" };
}
error[E0317]: `if` may be missing an `else` clause
--> b9.rs:4:17
|
4 | let label = if n > 2 { "big" };
| ^^^^^^^^^^^-----^^
| | |
| | found here
| expected `&str`, found `()`
|
= note: `if` expressions without `else` evaluate to `()`
= help: consider adding an `else` block that evaluates to the expected type
The Unit Value
An expression that has nothing meaningful to produce evaluates to (), the unit value. It is the only value of the unit type, which is also written ().
- A function without a return type returns
(). println!returns().- An
ifwithoutelseevaluates to(). - An assignment evaluates to
().
fn main() {
let a = 1;
let b = 2;
let unit = ();
let no_value = if a > b { println!("a is larger") };
println!("{unit:?} {no_value:?}");
}
() ()
Assignment Is an Expression of Type ()
In C, a = b = 3 assigns 3 to both variables because the assignment expression returns the assigned value. In Rust an assignment evaluates to (), so the same code does not work.
fn main() {
let mut x = 1;
println!("x = {x}");
let result = x = 5;
println!("result = {result:?}, x = {x}");
let compound = x += 1;
println!("compound = {compound:?}, x = {x}");
}
x = 1
result = (), x = 5
compound = (), x = 6
// ❌ does not compile
fn main() {
let mut a = 1;
let mut b = 2;
a = b = 3;
}
error[E0308]: mismatched types
--> a5.rs:5:9
|
3 | let mut a = 1;
| - expected due to this value
4 | let mut b = 2;
5 | a = b = 3;
| ^^^^^ expected integer, found `()`
Blocks and the Tail Expression
A block { ... } is an expression. It contains zero or more statements, optionally followed by one expression with no semicolon. That last expression is the tail expression, and its value becomes the value of the block. A block with no tail expression evaluates to ().
{
let a = 3; <- statement
let b = a * 2; <- statement
a + b <- tail expression: no semicolon, value of the block
}
A function body is a block, so the tail expression is the function's return value:
fn add_one(n: i32) -> i32 {
n + 1
}
fn main() {
println!("{}", add_one(1));
}
2
return is needed only for returning early. Adding a semicolon to the tail expression turns it into a statement. The block then has no tail expression and evaluates to ():
// ❌ does not compile
fn add_one(n: i32) -> i32 {
n + 1;
}
fn main() {
println!("{}", add_one(1));
}
error[E0308]: mismatched types
--> b6.rs:2:23
|
2 | fn add_one(n: i32) -> i32 {
| ------- ^^^ expected `i32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
3 | n + 1;
| - help: remove this semicolon to return this value
Expression Statements
Placing a semicolon after an expression makes it an expression statement. The expression is evaluated and its value is discarded.
fn main() {
let x = 3;
if x > 1 { 10 } else { 20 };
1 + 2;
x;
"text";
println!("{x}");
}
warning: unused arithmetic operation that must be used
--> a7.rs:4:5
|
4 | 1 + 2;
| ^^^^^ the arithmetic operation produces a value
|
= note: `#[warn(unused_must_use)]` (part of `#[warn(unused)]`) on by default
help: use `let _ = ...` to ignore the resulting value
|
4 | let _ = 1 + 2;
| +++++++
warning: path statement with no effect
--> a7.rs:5:5
|
5 | x;
| ^^
|
= note: `#[warn(path_statements)]` (part of `#[warn(unused)]`) on by default
warning: 2 warnings emitted
3
The code compiles, but the compiler warns about expression statements that compute a value and do nothing with it.
Block-Like Expressions as Statements
if, match, loop, while, for, and plain blocks can be statements without a trailing semicolon. In that position they must evaluate to ():
// ❌ does not compile
fn main() {
let x = 3;
if x > 1 { 10 } else { 20 }
println!("{x}");
}
error[E0308]: mismatched types
--> a6.rs:4:16
|
4 | if x > 1 { 10 } else { 20 }
| -----------^^--------------
| | |
| | expected `()`, found integer
| expected this to be `()`
error[E0308]: mismatched types
--> a6.rs:4:28
|
4 | if x > 1 { 10 } else { 20 }
| -----------------------^^--
| | |
| | expected `()`, found integer
| expected this to be `()`
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0308`.
Adding a semicolon after the closing brace, as in the previous example, discards the value and fixes the error.
Diverging Expressions
Some expressions never produce a value because they transfer control elsewhere: return, break, continue, panic!, and calls to functions that never return. Their type is !, the never type, which can be used in place of any other type. This lets one branch of an if or match leave the surrounding code while the other produces a value.
fn describe(n: i32) -> &'static str {
let text = if n > 0 {
"positive"
} else {
return "not positive";
};
text
}
fn main() {
let values = [3, -1, 4];
for value in values {
let doubled: i32 = if value > 0 { value * 2 } else { continue };
println!("{value} -> {doubled}");
}
println!("{}", describe(5));
println!("{}", describe(-5));
}
3 -> 6
4 -> 8
positive
not positive
In describe, the else branch returns from the function, so only the "positive" branch produces the value of text. In the loop, continue skips the rest of the iteration for negative numbers.
Place Expressions and Value Expressions
Rust distinguishes two categories of expression:
| Category | Meaning | Examples |
|---|---|---|
| Place expression | Refers to a memory location. It can appear on the left side of an assignment | x, numbers[0], point.x, *reference |
| Value expression | Produces a value and refers to no location | 5, a + b, square(4) |
Only a place expression can be assigned to:
struct Point {
x: i32,
y: i32,
}
fn main() {
let mut numbers = [1, 2, 3];
let mut point = Point { x: 0, y: 0 };
let mut value = 10;
let reference = &mut value;
numbers[0] = 100; // index expression is a place
point.x = 5; // field access is a place
*reference = 20; // dereference is a place
point.y = point.x + 1; // right side is a value
println!("{numbers:?} ({}, {}) {}", point.x, point.y, value);
}
[100, 2, 3] (5, 6) 20
Assigning to a value expression is an error:
// ❌ does not compile
fn main() {
let x = 1;
5 = x;
}
error[E0070]: invalid left-hand side of assignment
--> c1.rs:4:7
|
4 | 5 = x;
| - ^
| |
| cannot assign to this expression