W3cubDocs

/Rust

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

Lvalues and rvalues

Expressions are divided into two main categories: lvalues and rvalues. Likewise within each expression, sub-expressions may occur in lvalue context or rvalue context. The evaluation of an expression depends both on its own category and the context it occurs within.

An lvalue is an expression that represents a memory location. These expressions are paths which refer to local variables, static variables, function parameters, dereferences (*expr), array indexing expressions (expr[expr]), field references (expr.f) and parenthesized lvalue expressions. All other expressions are rvalues.

The left operand of an assignment or compound assignment expression is an lvalue 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 is also an lvalue context. All other expression contexts are rvalue contexts.

Moved and copied types

When an lvalue is evaluated in an rvalue context or is bound by value in a pattern, it denotes the value held in that memory location. If value is of a type that implements Copy, then the value will be copied. In the remaining situations if the type of the value is Sized it may be possible to move the value. Only the following lvalues may be moved out of:

Moving out of an lvalue deinitializes that location (if it comes from a local variable), so that it can't be read from again. In all other cases, trying to use an lvalue in an rvalue context is an error.

Mutability

For an lvalue to be assigned to, mutably borrowed, implicitly mutably borrowed or bound to a pattern containing ref mut it must be mutable, we call these contexts mutable lvalue contexts, other lvalue contexts are called immutable.

The following expressions can create mutable lvalues:

  • Mutable variables, which are not currently borrowed.
  • Mutable static items.
  • Temporary values.
  • Fields, this evaluates the subexpression in a mutable lvalue 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 for the next rule.
  • Dereferences of a type that implements DerefMut, this then requires that the value being dereferenced is evaluated is a mutable lvalue context.
  • Array indexing of a type that implements DerefMut, this then evaluates the value being indexed (but not the index) in mutable lvalue context.

Temporary lifetimes

When using an rvalue in most lvalue contexts, a temporary unnamed lvalue is created and used instead, if not promoted to 'static. Promotion of an rvalue 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 an if/else or in the loop conditional expression of a while expression.

When a temporary rvalue 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 statement (the 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 expression temp() is an rvalue. As it is being borrowed, a temporary is created which will be freed after the innermost enclosing statement (the let declaration, in this case).
  • let x = temp().foo(). This is the same as the previous example, except that the value of temp() is being borrowed via autoref on a method-call. Here we are assuming that foo() is an &self method defined in some trait, say Foo. In other words, the expression temp().foo() is equivalent to Foo::foo(&temp()).
  • let x = if foo(&temp()) {bar()} else {baz()};. The expression temp() is an rvalue. As the temporary is created in the condition expression of an if/else, it will be freed at the end of the condition expression (in this example before the call to bar or baz is made).
  • let x = if temp().must_run_bar {bar()} else {baz()};. Here we assume the type of temp() is a struct with a boolean field must_run_bar. As the previous example, the temporary corresponding to temp() will be freed at the end of the condition expression.
  • while foo(&temp()) {bar();}. The temporary containing the return value from the call to temp() 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 to bar if the loop body is executed).
  • let x = &temp(). Here, the same temporary is being assigned into x, 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 an lvalue 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:

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:

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 here also exist in core::ops and core::cmp with the same names.

© 2010 The Rust Project Developers
Licensed under the Apache License, Version 2.0 or the MIT license, at your option.
https://doc.rust-lang.org/reference/expressions.html