Operator Precedence
Operator precedence determines how an expression containing multiple operators is grouped when parentheses are omitted, and associativity determines the direction in which operators of the same precedence level are grouped (left-to-right, right-to-left, or non-associative).
Complete Rust Operator Precedence Table
The following table lists all Rust operators from highest precedence (1, binds tightest) to lowest precedence (15, binds loosest):
| Level | Category | Operators | Associativity | Example (a op b op c) |
|---|---|---|---|---|
| 1 | Paths, method calls, field access, indexing, call, ? |
:: . () [] ? |
Left-to-right | obj.items[0].parse()? |
| 2 | Unary prefix operators | - ! * & &mut |
Right-to-left (prefix) | &*ptr -> &(*ptr) |
| 3 | Type cast | as |
Left-to-right | x as i64 as f64 |
| 4 | Multiplicative arithmetic | * / % |
Left-to-right | a / b * c -> (a / b) * c |
| 5 | Additive arithmetic | + - |
Left-to-right | a - b + c -> (a - b) + c |
| 6 | Bitwise shifts | << >> |
Left-to-right | x << 2 >> 1 -> (x << 2) >> 1 |
| 7 | Bitwise AND | & |
Left-to-right | a & b & c -> (a & b) & c |
| 8 | Bitwise XOR | ^ |
Left-to-right | a ^ b ^ c -> (a ^ b) ^ c |
| 9 | Bitwise OR | | |
Left-to-right | a | b | c -> (a | b) | c |
| 10 | Relational comparisons | == != < > <= >= |
None (cannot chain!) | a == b |
| 11 | Logical AND | && |
Left-to-right | a && b && c -> (a && b) && c |
| 12 | Logical OR | || |
Left-to-right | a || b || c -> (a || b) || c |
| 13 | Ranges | .. ..= |
None (requires parens) | 0..10 |
| 14 | Assignment & compound assignment | = += -= *= /= %= &= |= ^= <<= >>= |
Right-to-left | x += y |
| 15 | Control-flow jumps | return break yeet |
Right-to-left (prefix) | return a + b -> return (a + b) |
Precedence vs Order of Evaluation
It is critical to distinguish precedence (how sub-expressions are grouped into a tree) from order of evaluation (the chronological sequence in which sub-expressions execute at runtime):
- Precedence decides that
f() + g() * h()is grouped asf() + (g() * h()). - Order of evaluation in Rust is left-to-right for binary operators: even though
g() * h()has higher precedence than+, Rust still callsf()first, theng(), thenh(), and finally multiplies the results ofg()andh()before adding tof()!
fn step(label: &str, value: i32) -> i32 {
println!(" evaluated {label} -> {value}");
value
}
fn main() {
println!("Computing step(\"A\", 2) + step(\"B\", 3) * step(\"C\", 4):");
let result = step("A", 2) + step("B", 3) * step("C", 4);
println!("Result: {result}");
}
Computing step("A", 2) + step("B", 3) * step("C", 4):
evaluated A -> 2
evaluated B -> 3
evaluated C -> 4
Result: 14
Important Precedence Interactions and Pitfalls
1. Method Calls (.) Bind Tighter Than Unary -, !, *, and as
Because . is at Level 1 (the very top of the table) while unary - and as are at Levels 2 and 3, method calls always execute before unary negation, dereferencing, or as casts:
| Written Expression | How Rust Parses It | Usually Intended Expression |
|---|---|---|
-4_i32.pow(2) |
-(4_i32.pow(2)) -> -16 |
(-4_i32).pow(2) -> +16 |
*ptr.field |
*(ptr.field) |
(*ptr).field (though . auto-dereferences ptr anyway) |
!flag.is_valid() |
!(flag.is_valid()) |
Matches expectation! |
x as f64.sqrt() |
Syntax error (f64.sqrt() invalid type) |
(x as f64).sqrt() |
(0..5).rev() |
Calls .rev() on 0..5 |
0..5.rev() would call .rev() on 5! |
fn main() {
// `.pow(2)` runs on `4_i32` FIRST, and unary `-` negates the result `16` -> `-16`!
let unparenthesized = -4_i32.pow(2);
// Parentheses force `-4_i32` to be squared: `(-4) * (-4)` -> `+16`
let parenthesized = (-4_i32).pow(2);
println!("-4_i32.pow(2) = {unparenthesized}");
println!("(-4_i32).pow(2) = {parenthesized}");
}
-4_i32.pow(2) = -16
(-4_i32).pow(2) = 16
2. Type Cast (as) Binds Tighter Than Arithmetic (+, -, *, /)
Because as (Level 3) has higher precedence than *, /, +, and - (Levels 4 and 5), writing a + b as f64 casts only b to f64, not a + b:
fn main() {
let a: i32 = 3;
let b: i32 = 2;
// `(a / b) as f64` performs integer division `3 / 2 == 1` FIRST, then casts `1` to `1.0`
let int_div_then_cast = (a / b) as f64;
// `a as f64 / b as f64` casts both operands first (`3.0 / 2.0`), producing `1.5`
let float_div = a as f64 / b as f64;
println!("(a / b) as f64 = {int_div_then_cast:.1}");
println!("a as f64 / b as f64 = {float_div:.1}");
}
(a / b) as f64 = 1.0
a as f64 / b as f64 = 1.5
3. Additive (+, -) Binds Tighter Than Bitwise Shifts (<<, >>)
In both C and Rust, + and - (Level 5) bind tighter than << and >> (Level 6). Therefore, 1 << 2 + 1 is parsed as 1 << (2 + 1) (8), not (1 << 2) + 1 (5):
fn main() {
let ungrouped = 1 << 2 + 1; // Parsed as 1 << (2 + 1) = 8
let grouped = (1 << 2) + 1; // 4 + 1 = 5
println!("1 << 2 + 1 = {ungrouped}");
println!("(1 << 2) + 1 = {grouped}");
}
1 << 2 + 1 = 8
(1 << 2) + 1 = 5
4. Bitwise Operators (&, ^, |) Bind Tighter Than Comparisons (==, !=)
Fixing a classic design flaw in C, Rust gives &, ^, and | (Levels 7–9) higher precedence than == and != (Level 10). Thus flags & MASK == MASK is parsed as (flags & MASK) == MASK rather than flags & (MASK == MASK).
Left-to-Right Associativity of -, /, and %
When operators of the exact same precedence level appear in a row, left-to-right associativity means they group from the left:
20 - 5 - 3is(20 - 5) - 3 = 12(not20 - (5 - 3) = 18).24 / 6 / 2is(24 / 6) / 2 = 2(not24 / (6 / 2) = 8).
fn main() {
println!("20 - 5 - 3 = {}", 20 - 5 - 3);
println!("24 / 6 / 2 = {}", 24 / 6 / 2);
}
20 - 5 - 3 = 12
24 / 6 / 2 = 2