Expressions
An expression may have two roles: it always produces a value, and it may have effects (otherwise known as "side effects"). An expression evaluates to a value, and has effects during evaluation. Many expressions contain sub-expressions (operands). The meaning of each kind of expression dictates several things:
- Whether or not to evaluate the sub-expressions when evaluating the expression
- The order in which to evaluate the sub-expressions
- How to combine the sub-expressions' values to obtain the value of the expression
In this way, the structure of expressions dictates the structure of execution. Blocks are just another kind of expression, so blocks, statements, expressions, and blocks again can recursively nest inside each other to an arbitrary depth.
Expression precedence
The precedence of Rust operators and expressions is ordered as follows, going from strong to weak. Binary Operators at the same precedence level are evaluated in the order given by their associativity.
Operator/Expression | Associativity |
---|---|
Paths | |
Method calls | |
Field expressions | left to right |
Function calls, array indexing | |
? | |
Unary - * ! & &mut | |
as : | left to right |
* / % | left to right |
+ - | left to right |
<< >> | left to right |
& | left to right |
^ | left to right |
| | left to right |
== != < > <= >= | Require parentheses |
&& | left to right |
|| | left to right |
.. ... | Require parentheses |
<- | right to left |
= += -= *= /= %= &= |= ^= <<= >>= | right to left |
return break closures |
Place Expressions and Value Expressions
Expressions are divided into two main categories: place expressions and value expressions. Likewise within each expression, sub-expressions may occur in either place context or value context. The evaluation of an expression depends both on its own category and the context it occurs within.
A place expression is an expression that represents a memory location. These
expressions are paths which refer to local variables, static variables,
dereferences (*expr
), array indexing expressions (expr[expr]
),
field references (expr.f
) and parenthesized place expressions. All other
expressions are value expressions.
A value expression is an expression that represents an actual value.
The left operand of an assignment or compound assignment expression is a place expression context, as is the single operand of a unary borrow, and the operand of any implicit borrow. The discriminant or subject of a match expression and right side of a let statement is also a place expression context. All other expression contexts are value expression contexts.
Note: Historically, place expressions were called lvalues and value expressions were called rvalues.
Moved and copied types
When a place expression is evaluated in a value expression context, or is bound
by value in a pattern, it denotes the value held in that memory location. If
the type of that value implements Copy
, then the value will be copied. In
the remaining situations if that type is Sized
, then it may be possible to
move the value. Only the following place expressions may be moved out of:
- Variables which are not currently borrowed.
- Temporary values.
- Fields of a place expression which can be moved out of and
doesn't implement
Drop
. - The result of dereferencing an expression with type
Box<T>
and that can also be moved out of.
Moving out of a place expression that evaluates to a local variable, the location is deinitialized and cannot be read from again until it is reinitialized. In all other cases, trying to use a place expression in a value expression context is an error.
Mutability
For a place expression to be assigned to, mutably borrowed,
implicitly mutably borrowed, or bound to a pattern containing ref mut
it
must be mutable. We call these mutable place expressions. In contrast,
other place expressions are called immutable place expressions.
The following expressions can be mutable place expression contexts:
- Mutable variables, which are not currently borrowed.
- Mutable
static
items. - Temporary values.
- Fields, this evaluates the subexpression in a mutable place expression context.
- Dereferences of a
*mut T
pointer. - Dereference of a variable, or field of a variable, with type
&mut T
. Note: This is an exception to the requirement of the next rule. - Dereferences of a type that implements
DerefMut
, this then requires that the value being dereferenced is evaluated is a mutable place expression context. - Array indexing of a type that implements
DerefMut
, this then evaluates the value being indexed, but not the index, in mutable place expression context.
Temporary lifetimes
When using a value expression in most place expression contexts, a temporary
unnamed memory location is created initialized to that value and the expression
evaluates to that location instead, except if promoted to 'static
. Promotion
of a value expression to a 'static
slot occurs when the expression could be
written in a constant, borrowed, and dereferencing that borrow where the
expression was the originally written, without changing the runtime behavior.
That is, the promoted expression can be evaluated at compile-time and the
resulting value does not contain interior mutability or destructors (these
properties are determined based on the value where possible, e.g. &None
always has the type &'static Option<_>
, as it contains nothing disallowed).
Otherwise, the lifetime of temporary values is typically
- the innermost enclosing statement; the tail expression of a block is considered part of the statement that encloses the block, or
- the condition expression or the loop conditional expression if the
temporary is created in the condition expression of an
if
or in the loop conditional expression of awhile
expression.
When a temporary value expression is being created that is assigned into a
let
declaration, however, the temporary is created with the lifetime of
the enclosing block instead, as using the enclosing let
declaration
would be a guaranteed error (since a pointer to the temporary
would be stored into a variable, but the temporary would be freed before the
variable could be used). The compiler uses simple syntactic rules to decide
which values are being assigned into a let
binding, and therefore deserve a
longer temporary lifetime.
Here are some examples:
let x = foo(&temp())
. The expressiontemp()
is a value expression. As it is being borrowed, a temporary is created which will be freed after the innermost enclosing statement; in this case, thelet
declaration.let x = temp().foo()
. This is the same as the previous example, except that the value oftemp()
is being borrowed via autoref on a method-call. Here we are assuming thatfoo()
is an&self
method defined in some trait, sayFoo
. In other words, the expressiontemp().foo()
is equivalent toFoo::foo(&temp())
.let x = if foo(&temp()) {bar()} else {baz()};
. The expressiontemp()
is a value expression. As the temporary is created in the condition expression of anif
, it will be freed at the end of the condition expression; in this example before the call tobar
orbaz
is made.let x = if temp().must_run_bar {bar()} else {baz()};
. Here we assume the type oftemp()
is a struct with a boolean fieldmust_run_bar
. As the previous example, the temporary corresponding totemp()
will be freed at the end of the condition expression.while foo(&temp()) {bar();}
. The temporary containing the return value from the call totemp()
is created in the loop conditional expression. Hence it will be freed at the end of the loop conditional expression; in this example before the call tobar
if the loop body is executed.let x = &temp()
. Here, the same temporary is being assigned intox
, rather than being passed as a parameter, and hence the temporary's lifetime is considered to be the enclosing block.let x = SomeStruct { foo: &temp() }
. As in the previous case, the temporary is assigned into a struct which is then assigned into a binding, and hence it is given the lifetime of the enclosing block.let x = [ &temp() ]
. As in the previous case, the temporary is assigned into an array which is then assigned into a binding, and hence it is given the lifetime of the enclosing block.let ref x = temp()
. In this case, the temporary is created using a ref binding, but the result is the same: the lifetime is extended to the enclosing block.
Implicit Borrows
Certain expressions will treat an expression as a place expression by implicitly
borrowing it. For example, it is possible to compare two unsized [slices] for
equality directly, because the ==
operator implicitly borrows it's operands:
# #![allow(unused_variables)] #fn main() { # let c = [1, 2, 3]; # let d = vec![1, 2, 3]; let a: &[i32]; let b: &[i32]; # a = &c; # b = &d; // ... *a == *b; // Equivalent form: ::std::cmp::PartialEq::eq(&*a, &*b); #}
Implicit borrows may be taken in the following expressions:
- Left operand in method-call expressions.
- Left operand in field expressions.
- Left operand in call expressions.
- Left operand in array indexing expressions.
- Operand of the dereference operator (
*
). - Operands of comparison.
- Left operands of the compound assignment.
Constant expressions
Certain types of expressions can be evaluated at compile time. These are called
constant expressions. Certain places, such as in
constants and statics,
require a constant expression, and are always evaluated at compile time. In
other places, such as in let
statements,
constant expressions may be evaluated at compile time. If errors, such as out
of bounds array indexing or overflow occurs,
then it is a compiler error if the value must be evaluated at compile time,
otherwise it is just a warning, but the code will most likely panic when run.
The following expressions are constant expressions, so long as any operands are
also constant expressions and do not cause any Drop::drop
calls
to be ran.
- Literals.
- Paths to functions and constants. Recursively defining constants is not allowed.
- Tuple expressions.
- Array expressions.
- Struct expressions.
- Enum variant expressions.
- Block expressions, including
unsafe
blocks, which only contain items and possibly a constant tail expression. - Field expressions.
- Index expressions, array indexing or slice with a
usize
. - Range expressions.
- Closure expressions which don't capture variables from the environment.
- Built in negation, arithmetic, logical, comparison or lazy boolean
operators used on integer and floating point types,
bool
andchar
. - Shared borrows, except if applied to a type with interior mutability.
- The dereference operator.
- Grouped expressions.
- Cast expressions, except pointer to address and function pointer to address casts.
Overloading Traits
Many of the following operators and expressions can also be overloaded for
other types using traits in std::ops
or std::cmp
. These traits also
exist in core::ops
and core::cmp
with the same names.