5.3 Expressions and Statements
Rust makes a clearer distinction between expressions and statements than C/C++.
5.3.1 Expressions
An expression evaluates to a value. Most code constructs in Rust are expressions, including:
- Literals (
5
,true
,"hello"
) - Arithmetic (
x + y
) - Function calls (
calculate(a, b)
) - Comparisons (
a > b
) - Block expressions (
{ let temp = x * 2; temp + 1 }
) - Control flow constructs like
if
,match
, andloop
(thoughloop
itself often doesn’t evaluate to a useful value unless broken with one).
// These are all expressions:
5
x + 1
is_valid(data)
if condition { value1 } else { value2 }
{ // This whole block is an expression
let intermediate = compute();
intermediate * 10 // The block evaluates to this value
}
Critically, an expression by itself is not usually valid Rust code. It needs to be part of a statement (like an assignment or a function call) or used where a value is expected (like the right side of =
or a function argument).
5.3.2 Statements
A statement performs an action but does not evaluate to a useful value. Statements end with a semicolon (;
). The semicolon effectively discards the value of the preceding expression, making the overall construct evaluate to the unit type ()
.
Common statement types:
- Declaration Statements: Introduce items like variables, functions, structs, etc.
let x = 5;
(Variable declaration statement)fn my_func() {}
(Function definition statement)struct Point { x: i32, y: i32 }
(Struct definition statement)
- Expression Statements: An expression followed by a semicolon. This is used when you care only about the side effect of the expression (like calling a function that modifies state or performs I/O) and want to discard its return value.
do_something();
(Callsdo_something
, discards its return value)x + 1;
(Calculatesx + 1
, discards the result - usually pointless unless+
is overloaded with side effects)
Key Difference from C/C++: Assignment (=
) is a statement in Rust, not an expression. It does not evaluate to the assigned value. This prevents code like x = y = 5;
(which works in C) and avoids potential bugs related to assignment within conditional expressions (if (x = 0)
).
#![allow(unused)] fn main() { fn do_something() -> i32 { 0 } let mut x = 0; let y = 10; // Declaration statement x = y + 5; // Assignment statement (the expression y + 5 is evaluated, then assigned to x) do_something(); // Expression statement (calls function, discards result) }
5.3.3 Block Expressions
A code block enclosed in curly braces { ... }
is itself an expression. Its value is the value of the last expression within the block.
- If the last expression lacks a semicolon, the block evaluates to the value of that expression.
- If the last expression has a semicolon, or if the block is empty, the block evaluates to the unit type
()
.
fn main() { let y = { let x = 3; x + 1 // No semicolon: the block evaluates to x + 1 (which is 4) }; println!("y = {}", y); // Prints: y = 4 let z = { let x = 3; x + 1; // Semicolon: the value is discarded, block evaluates to () }; println!("z = {:?}", z); // Prints: z = () let w = { }; // Empty block evaluates to () println!("w = {:?}", w); // Prints: w = () }
This feature is powerful, allowing if
, match
, and even simple blocks to be used directly in assignments or function arguments. Be mindful of the final semicolon; omitting or adding it changes the block’s resulting value and type.
5.3.4 Line Structure
Rust is free-form regarding whitespace and line breaks. Statements are terminated by semicolons, not newlines.
#![allow(unused)] fn main() { // Valid, spans multiple lines let sum = 10 + 20 + 30 + 40; // Valid, multiple statements on one line (discouraged for readability) let a = 1; let b = 2; println!("Sum: {}", a + b); }