Rust: expression vs statement
Statements are instructions that do something, they don’t return a value. Expressions evaluate to a value, they return that value.
Rust is an expression-oriented language. This means that most things are expressions, and evaluate to some kind of value. However, there are also statements.
Assigning a value to a variable is a statement, it doesn’t return anything.
let num = 5;
Expressions can be part of a statement.
While the line above is a statement, it contains an expression (something that evaluates to a value).
In this case, the value itself, the integer 5
.
Function bodies are made up of a series of statements, optionally ending in an expression.
Expressions do not include ending semicolons. If you add a semicolon to the end of an expression, you turn it into a statement, which will then not return a value.
If a function ends in an expression, it returns the value of that expression.
A function definition is a statement, it does not result in a value. Calling a function is an expression, that expression evaluates to whatever that function call returns.
let num = add(4, 1);fn add(x: i32, y:i32) -> i32 {x + y}
The lines where the add
function is defined, are a statement, those lines don’t evaluate to anything.
Calling add(4, 1)
is an expression, it evaluates to a value (the integer 5
).
Inside the function, the last line of the function body is an expression x + y
.
That ending expression evaluates to a value and the function returns it.
The value add(4, 1)
evaluated to is then assigned to the variable named num
.
That entire line (ie. let num = add(4, 1);
) is a statement.
A codeblock that creates a new scope (ie. {}
) is an expression (it evaluates to a value).
let num = {let x = 4;x + 1};
Inside that codeblock, a variable declaration statement happens, followed by an expression (x + 1
).
The value that last expression evaluates to will be the value the entire codeblock evaluates to.
That value is then assigned to the num
variable in this example.
You may explicitly return a value from a function by using the return
keyword followed by an expression.
By convention, that line is terminated with a semicolon.
As you might expect, that isn’t mandatory and the compiler won’t complain if you leave it off.
This is an add
function that explicitly ends execution and returns a value is equivalent to the add
above that ended with an expression.
fn add(x: i32, y:i32) -> i32 {return x + y;}
We can use this mechanism to make code that has a variable declaration that is only used to later be populated a bit shorter.
fn main() {let num = 5;let mut name = "";if num > 3 {name = "Tom";} else {name = "Jerry";}println!("{}", name);}
if
is an expression, it returns a value.
The value an if
evaluates to is the value of the codeblock it executed.
That means we can rewrite our example above.
Now, name
is never an empty string, it’s either "Tom"
or "Jerry"
.
fn main() {let num = 5;let name = if num > 3 {"Tom"} else {"Jerry"};println!("{}", name);}
This means that every arm of the if
statement must return the same type.
If we return different types from the branches, the compiler won’t be able to figure out what the resulting type of the entire if
is and will show a compilation error.
A similar approach can be taken with more programming constructs in Rust that are expressions: like match
.