Rust is an expression language: most things that are statements elsewhere, such as
if, match and blocks, produce a value. An expression is
either with block (one ending in }, which can stand as a
statement without a semicolon) or without block (Reference: Expressions).
Control flow expressions are on the
Statements and control flow
page.
Operator precedence
Rows are listed from the strongest binding to the weakest (Reference: Expressions). Binary operators at the same level are grouped according to the associativity shown.
| Operator or expression | Associativity |
|---|---|
| Paths | |
| Method calls | |
| Field expressions | Left to right |
| Function calls, array indexing | |
? | |
Unary - ! * borrow | |
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 |
return break closures |
Comparison and range operators do not chain: a == b == c and
a..b..c are errors, and parentheses are required.
Place and value expressions
A place expression represents a memory location: a variable, a static, a
dereference *expr, an index expr[expr], a field
expr.f, or a parenthesised place. All others are value
expressions. A third, minor category, assignee expressions, is used on
the left of a destructuring assignment.
A place is expected as the left operand of a compound assignment, the operand of a
unary borrow, raw borrow or dereference, the operand of a field expression, the
indexed operand of an array index, the operand of an implicit borrow, the initialiser
of a let, the scrutinee of if let, match and
while let, and the base of a functional update.
If a value expression is used where a place is expected, a temporary is created, and is usually dropped at the end of the enclosing statement. A place used in a value context is copied or moved; see Moves and copies.
Evaluation order
Operands are evaluated before the expression’s effect, and left to right as written. This applies to the dereference, try, negation, arithmetic, comparison, cast, grouped, array, await, index, tuple, tuple index, struct, call, method call, field, break, range and return expressions. The only operators that may skip an operand are the lazy boolean ones.
Blocks as expressions
A block is a sequence of statements and an optional final expression, in braces. The
final expression, if there is one, is the block’s value; a block with no final
expression has type (), unless its end is unreachable.
let x = {
let a = 2;
let b = 3;
a * b // no semicolon: the value of the block
}; // x is 6
let y = if x > 5 { "big" } else { "small" };
let label = match x {
0 => "zero",
n if n < 0 => "negative",
_ => "positive",
};
if and match produce values| Form | Meaning |
|---|---|
{ … } | Plain block |
unsafe { … } | Block allowed to do unsafe operations |
const { … } | Evaluated at compile time |
async { … }, async move { … } | Creates a future (edition 2018 and later) |
'label: { … } | Labelled block; break 'label value leaves it early |
Operators
Borrow operators
&x and &mut x create references. Applied to a value
expression, they create a temporary. && is one token, but it acts
as two borrows in this position. These operators cannot be overloaded.
&raw const x and &raw mut x create raw pointers
directly, without making a reference first, which is needed when a reference would be
invalid, such as to a field of a packed struct.
Dereference
*x denotes the place a pointer points to. It works on references,
raw pointers (which requires unsafe) and Box. A value may
be moved out of *b when b is a Box. On other
types *x means *Deref::deref(&x), or
*DerefMut::deref_mut(&mut x) in a mutable context.
Arithmetic, bit and logical operators
| Operator | Integer | bool | Float | Trait |
|---|---|---|---|---|
+ | Addition | Addition | Add / AddAssign | |
- | Subtraction | Subtraction | Sub / SubAssign | |
* | Multiplication | Multiplication | Mul / MulAssign | |
/ | Division, rounding toward zero | Division | Div / DivAssign | |
% | Remainder; takes the sign of the dividend | Remainder | Rem / RemAssign | |
& | Bitwise and | Logical and | BitAnd / BitAndAssign | |
| | Bitwise or | Logical or | BitOr / BitOrAssign | |
^ | Exclusive or | Logical exclusive or | BitXor / BitXorAssign | |
<< | Left shift | Shl / ShlAssign | ||
>> | Right shift; arithmetic for signed, logical for unsigned | Shr / ShrAssign | ||
Unary - | Negation (signed) | Negation | Neg | |
Unary ! | Bitwise not | Logical not | Not |
Overflow. Integer overflow panics when overflow checks are on,
which they are by default in debug builds. This covers +,
* and binary -; unary - on the most negative
value (unless the operand is a literal); and shifts by a negative amount or by the
bit width or more. / and % of the smallest signed value by
-1, and division by zero, always panic. In release builds overflow
wraps by default.
Comparison
| Operator | Trait method |
|---|---|
==, != | PartialEq::eq, PartialEq::ne |
<, >, <=, >= | PartialOrd::lt, gt, le, ge |
Operands are evaluated as places and implicitly borrowed, so a comparison does not
move its operands. a == b is equivalent to
PartialEq::eq(&a, &b).
Lazy boolean operators
|| and && take bool operands. The right
operand is evaluated only when needed: for || when the left is
false, for && when the left is true.
Type cast expressions
TypeCastExpression: Expression as TypeNoBounds
as performs coercions and the explicit casts below. Any other cast is
an error.
| From | To | Result |
|---|---|---|
| Integer or float | Integer or float | Numeric cast (below) |
| Unit-only or field-less enum | Integer | Its discriminant, then a numeric cast |
bool | Integer | false is 0, true is 1 |
char | Integer | Its code point, then a numeric cast |
u8 | char | The character with that code point |
*T | *U | Pointer to pointer, with compatible metadata |
| Sized pointer | Integer | The address, truncated if necessary |
| Integer | Sized pointer | The integer as an address |
| Function item or pointer | Pointer or integer | The function’s address |
| Non-capturing closure | Function pointer |
| Numeric cast | Effect |
|---|---|
| Same-size integers | Bits unchanged: -1i8 as u8 is 255 |
| Larger integer to smaller | Truncates: 1234u16 as u8 is 210 |
| Smaller integer to larger | Zero-extends if the source is unsigned, sign-extends if signed |
| Float to integer | Rounds toward zero; NaN gives 0; out-of-range values saturate |
| Integer to float | Nearest value, ties to even |
f32 to f64 | Exact |
f64 to f32 | Nearest value, ties to even; overflow gives infinity |
Assignment
place = value has type (). The right side is evaluated
first, then the place, then the old value is dropped, unless the place is
uninitialised, and the new value is moved in.
Destructuring assignment takes apart the right-hand side into existing places:
let (mut a, mut b) = (0, 1);
(b, a) = (a, b); // swap
struct S { x: u32, y: u32 }
S { x: a, y: b } = S { x: 3, y: 4 };
[a, .., b] = [1, 2, 3, 4];
(a, _) = (5, 6); // _ ignores a value
The left side is an assignee expression, not a pattern, and it must correspond to an irrefutable pattern.
Compound assignment
place op= value requires a place on the left and has type
(). For primitive operand types the right side is evaluated first; if
the operation goes through a trait such as AddAssign (including
generics that resolve to a primitive), the left is evaluated first. The Reference
warns against relying on this order.
The try operator
Expression ?
? applied to a Result or Option returns the
success value, or returns early from the enclosing function, closure or
async block with the failure. It uses the unstable Try trait,
so user types cannot implement it.
| Type | Continues with | Returns early with |
|---|---|---|
Result<T, E> | v from Ok(v) | Err(From::from(e)) |
Option<T> | v from Some(v) | None |
ControlFlow<B, C> | c from Continue(c) | Break(b) |
Poll<Result<T, E>> | Ready(v) or Pending | Ready(Err(From::from(e))) |
Poll<Option<Result<T, E>>> | Ready(Some(v)), Ready(None) or Pending | Ready(Some(Err(From::from(e)))) |
fn read_number(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
let text = std::fs::read_to_string(path)?; // io::Error converted by From
let n: i32 = text.trim().parse()?; // ParseIntError converted by From
Ok(n)
}
? operator converts each error with FromThe error conversion is why a function can use ? on several error types,
provided its return type has a From for each. See
Error handling.
Closures
ClosureExpression:
async? move? ( || | | ClosureParameters? | )
( Expression | -> TypeNoBounds BlockExpression )
ClosureParam:
OuterAttribute* PatternNoTopAlt ( : Type )?
let add = |x, y| x + y;
let typed = |x: i32| -> i32 { x + 1 };
let mut count = 0;
let mut bump = || count += 1; // captures count by mutable reference
bump();
bump();
let word = String::from("hi");
let owned = move || println!("{word}"); // moves word into the closure
- Parameters are irrefutable patterns with optional types. A return type requires the body to be a block. Types may be inferred from use.
- A closure captures variables from its surroundings, by shared reference, unique
borrow, mutable reference or move, whichever is the least the body needs. The
movekeyword forces capture by value. - From edition 2021, a closure captures disjoint fields, such as
a.x, instead of the whole variablea. In earlier editions it captures the whole variable. - Every closure implements
FnOnce; those that do not move out of a capture implementFnMut; those that also do not mutate a capture implementFn. The trait depends on what the body does, not on whether the closure is written withmove. - Non-capturing, non-async closures coerce to function pointers.
- Closures are
CopyandCloneif their captures are. Closures have no fixed layout. asyncclosures return a future when called, and implementAsyncFn,AsyncFnMutandAsyncFnOnce. They are not allowed in edition 2015.
Ranges
The range syntax builds values of standard library types in
std::ops.
| Expression | Type | Values of x |
|---|---|---|
start..end | Range | start ≤ x < end |
start.. | RangeFrom | start ≤ x |
..end | RangeTo | x < end |
.. | RangeFull | All |
start..=end | RangeInclusive | start ≤ x ≤ end |
..=end | RangeToInclusive | x ≤ end |
for i in 1..11 { println!("{i}"); } // 1 to 10
let s = "hello";
let tail = &s[1..]; // "ello"
let head = &s[..=2]; // "hel"
Other expressions
| Form | Notes |
|---|---|
| Literal | See Literals |
| Path | A name such as x, std::mem::swap or <T as Trait>::f |
| Tuple | (1, "a"); a one-element tuple needs a comma: (1,) |
| Tuple index | t.0, on tuples and tuple structs |
| Array | [1, 2, 3], or [0; 4]. The repeat operand is evaluated once and copied, so it must be Copy when the length is more than one. A path to a const item is evaluated once per element instead, and not at all when the length is zero |
| Index | a[i], using Index or IndexMut for non-array types; out-of-bounds panics |
| Struct | See Struct expressions |
| Field access | x.f; automatically dereferences |
| Function call | f(a, b); callable through Fn, FnMut or FnOnce |
| Method call | x.foo(a); the receiver is automatically referenced or dereferenced |
.await | See Concurrency and async |
return, break, continue | Have type ! |
| Underscore | _, usable only on the left of an assignment |
| Macro invocation | name!(…), see Macros |
Constant expressions
A const context requires a constant expression and is always evaluated at compile time (Reference: Constant evaluation). The const contexts are:
- Array type length expressions and array repeat lengths
- The initialisers of
constitems,staticitems and enum discriminants - Const generic arguments
constblocks
Constant expressions may use literals, const generic parameters, paths to functions
and constants, tuples, arrays, struct expressions, blocks with let,
built-in operators, loop, while, if and
match, and calls to const fn. Closures that capture their
environment are not allowed.
- A
const fnruns at compile time when called in a const context, using the target’s types, and at run time otherwise. - Overflow and out-of-bounds indexing are compile-time errors in a const context.
- Statics cannot be written during constant evaluation,
externstatics cannot be read, and mutable statics can only be read inside a static’s initialiser. - An expression that runs
Drop::dropis not a constant expression.