Rust operators and expressions
last modified October 25, 2026
In this article we show how to use operators and expressions in Rust.
An operator is a symbol that performs an operation on one or more values, called operands. An expression is a piece of code that evaluates to a value; it is built from literals, variables, operators, and function calls. The order in which the operators of an expression are evaluated is given by their precedence and associativity.
Rust is an expression-oriented language. Besides arithmetic, also blocks,
if, and match are expressions that produce a value.
Rust has no increment and decrement operators (++ and
--) and no ternary operator.
The examples were tested with Rust 1.97 and the 2024 edition. They are run as
Cargo projects with cargo run -q.
Unary operators
Unary operators work with a single operand.
-negation of a number!logical NOT for booleans, bitwise NOT for integers*dereference; access to the value a reference points to&and&mutborrow; create a shared or a mutable reference
fn main() {
let a = 1;
println!("{}", -a);
println!("{}", -(-a));
let is_married = false;
println!("{}", !is_married);
let bits: u8 = 0b0000_1111;
println!("{:08b}", !bits);
let val = 5;
let r = &val;
println!("{}", *r + 1);
}
The example presents the unary minus, the not operator, and dereferencing.
let bits: u8 = 0b0000_1111;
println!("{:08b}", !bits);
For integers, the ! operator flips every bit. Rust has no
separate ~ operator as C has.
let r = &val;
println!("{}", *r + 1);
The & operator creates a reference to val; the
* operator reads the value behind it.
$ cargo run -q -1 1 true 11110000 6
Arithmetic operators
The arithmetic operators are +, -, *,
/, and % (remainder).
fn main() {
let a = 17;
let b = 5;
println!("{a} + {b} = {}", a + b);
println!("{a} - {b} = {}", a - b);
println!("{a} * {b} = {}", a * b);
println!("{a} / {b} = {}", a / b);
println!("{a} % {b} = {}", a % b);
println!("17.0 / 5.0 = {}", 17.0 / 5.0);
println!("-17 / 5 = {}", -17 / 5);
println!("-17 % 5 = {}", -17 % 5);
println!("(-17_i32).rem_euclid(5) = {}", (-17_i32).rem_euclid(5));
}
The program applies the arithmetic operators to two integers.
println!("{a} / {b} = {}", a / b);
Division of two integers is an integer division; the fractional part is discarded. To get 3.4, the operands must be floating-point numbers.
println!("-17 / 5 = {}", -17 / 5);
println!("-17 % 5 = {}", -17 % 5);
Integer division rounds toward zero, and the remainder has the sign of the left operand.
println!("(-17_i32).rem_euclid(5) = {}", (-17_i32).rem_euclid(5));
The rem_euclid method returns a remainder that is never
negative, which is often what we need, for instance when computing positions
in a circular buffer.
$ cargo run -q 17 + 5 = 22 17 - 5 = 12 17 * 5 = 85 17 / 5 = 3 17 % 5 = 2 17.0 / 5.0 = 3.4 -17 / 5 = -3 -17 % 5 = -2 (-17_i32).rem_euclid(5) = 3
Mixing types
Rust does not convert numeric types implicitly. Both operands of an arithmetic
operator must have the same type; conversions are written with the
as operator.
fn main() {
let count: i32 = 7;
let price: f64 = 2.5;
let total = count as f64 * price;
println!("{total}");
let sum = 10;
let n = 4;
println!("{} {}", sum / n, sum as f64 / n as f64);
let big: i32 = 300;
println!("{}", big as u8);
println!("{}", -1_i32 as u32);
println!("{}", 3.99_f64 as i32);
}
We multiply an integer by a floating-point number and convert between integer types.
let total = count as f64 * price;
The expression count * price would not compile, because it
multiplies an i32 by an f64. We convert the count
first. The as operator has a higher precedence than
*.
println!("{}", big as u8);
println!("{}", -1_i32 as u32);
println!("{}", 3.99_f64 as i32);
The as conversions never fail, but they can lose information. A
value too large for the target integer type is truncated to its lowest bits:
300 becomes 44. A negative number cast to an unsigned type is reinterpreted,
and a float is truncated toward zero. For checked conversions, there are the
TryFrom and TryInto traits.
$ cargo run -q 17.5 2 2.5 44 4294967295 3
Integer overflow
When the result of an integer operation does not fit into its type, an
overflow occurs. In debug builds, which cargo run creates by
default, overflow causes a panic; in release builds, the value silently wraps
around. The integer types have methods that make the intended behaviour
explicit.
fn main() {
let x: u8 = 250;
println!("{:?}", x.checked_add(10));
println!("{:?}", x.checked_add(5));
println!("{}", x.wrapping_add(10));
println!("{}", x.saturating_add(10));
println!("{:?}", x.overflowing_add(10));
let y: u8 = "10".parse().unwrap();
println!("{}", x + y);
}
The maximum value of a u8 is 255. We add 10 to 250 in several
ways.
println!("{:?}", x.checked_add(10));
println!("{:?}", x.checked_add(5));
The checked_add method returns None on overflow and
Some with the result otherwise.
println!("{}", x.wrapping_add(10));
println!("{}", x.saturating_add(10));
println!("{:?}", x.overflowing_add(10));
The wrapping_add method wraps around past the maximum,
saturating_add stops at the maximum, and
overflowing_add returns the wrapped value together with a flag
that tells whether an overflow occurred.
let y: u8 = "10".parse().unwrap();
println!("{}", x + y);
The plain + operator panics in a debug build. The value of
y is parsed at runtime; with a constant, the compiler would
report the overflow already at compile time.
$ cargo run -q None Some(255) 4 255 (4, true) thread 'main' (25948) panicked at src/main.rs:11:20: attempt to add with overflow note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Comparison operators
Comparison operators compare two values and return a bool.
fn main() {
let a = 10;
let b = 20;
println!("{a} == {b}: {}", a == b);
println!("{a} != {b}: {}", a != b);
println!("{a} > {b}: {}", a > b);
println!("{a} < {b}: {}", a < b);
println!("{a} >= {b}: {}", a >= b);
println!("{a} <= {b}: {}", a <= b);
println!("{}", "apple" < "banana");
println!("{}", (1, 5) < (2, 0));
println!("{:?}", a.cmp(&b));
}
The program compares two integers with all six comparison operators.
println!("{}", "apple" < "banana");
println!("{}", (1, 5) < (2, 0));
Strings are compared lexicographically, by their bytes. Tuples, arrays, and other compound values are compared element by element; the first pair of different elements decides.
println!("{:?}", a.cmp(&b));
The cmp method returns an Ordering:
Less, Equal, or Greater. It is used in
sorting functions and in match expressions.
$ cargo run -q 10 == 20: false 10 != 20: true 10 > 20: false 10 < 20: true 10 >= 20: false 10 <= 20: true true true Less
Logical operators
The logical operators && (AND), || (OR), and
! (NOT) combine boolean values.
fn is_even(n: i32) -> bool {
println!(" checking {n}");
n % 2 == 0
}
fn main() {
let a = true;
let b = false;
println!("a && b: {}", a && b);
println!("a || b: {}", a || b);
println!("!a: {}", !a);
println!("false && is_even(4):");
println!("{}", false && is_even(4));
println!("true && is_even(4):");
println!("{}", true && is_even(4));
}
The is_even function prints a line each time it is called, so we
can see when it is evaluated.
println!("{}", false && is_even(4));
The && and || operators are
short-circuiting: the right operand is evaluated only when the left
one does not decide the result. Since the left operand is
false, the whole expression is false and
is_even is not called.
println!("{}", true && is_even(4));
Here the left operand is true, so the right operand must be
evaluated; the output shows the "checking 4" line.
$ cargo run -q a && b: false a || b: true !a: false false && is_even(4): false true && is_even(4): checking 4 true
Bitwise operators
Bitwise operators work with the individual bits of integers: &
(AND), | (OR), ^ (XOR), << (left
shift), and >> (right shift).
fn main() {
let a: u8 = 0b1100;
let b: u8 = 0b1010;
println!("{:04b} & {:04b} = {:04b}", a, b, a & b);
println!("{:04b} | {:04b} = {:04b}", a, b, a | b);
println!("{:04b} ^ {:04b} = {:04b}", a, b, a ^ b);
println!("{:04b} << 2 = {:06b}", a, a << 2);
println!("{:04b} >> 2 = {:04b}", a, a >> 2);
let flags = 0b0101_u8;
let has_third = flags & 0b0100 != 0;
println!("third bit set: {has_third}");
}
We apply the bitwise operators to two four-bit patterns and print the results in binary.
println!("{:04b} << 2 = {:06b}", a, a << 2);
Shifting left by two places multiplies the value by four; shifting right by two divides it by four.
let has_third = flags & 0b0100 != 0;
A common use of & is testing a bit with a mask. Unlike in C,
the bitwise operators have a higher precedence than comparisons in Rust, so no
parentheses are needed.
$ cargo run -q 1100 & 1010 = 1000 1100 | 1010 = 1110 1100 ^ 1010 = 0110 1100 << 2 = 110000 1100 >> 2 = 0011 third bit set: true
Compound assignment operators
Compound assignment operators combine an operation with an assignment;
a += 5 is a shorter form of a = a + 5. The variable
must be mutable.
fn main() {
let mut a = 10;
a += 5;
println!("a += 5 -> {a}");
a -= 3;
println!("a -= 3 -> {a}");
a *= 4;
println!("a *= 4 -> {a}");
a /= 6;
println!("a /= 6 -> {a}");
a %= 5;
println!("a %= 5 -> {a}");
a <<= 3;
println!("a <<= 3 -> {a}");
let mut s = String::from("sky");
s += " is blue";
println!("{s}");
}
The program changes a mutable integer with several compound assignment operators.
a <<= 3;
The bitwise operators have compound forms too: &=,
|=, ^=, <<=, and
>>=.
let mut s = String::from("sky");
s += " is blue";
The += operator also appends a string slice to a
String.
$ cargo run -q a += 5 -> 15 a -= 3 -> 12 a *= 4 -> 48 a /= 6 -> 8 a %= 5 -> 3 a <<= 3 -> 24 sky is blue
Range operators
The .. and ..= operators create ranges. A range is a
value; it is used in loops, for slicing, and in patterns.
fn main() {
let r1 = 1..5;
let r2 = 1..=5;
println!("{:?}", r1.clone().collect::<Vec<_>>());
println!("{:?}", r2.clone().collect::<Vec<_>>());
println!("{} {}", r1.contains(&5), r2.contains(&5));
let word = "falcon";
println!("{} {} {}", &word[..3], &word[3..], &word[1..=2]);
}
We create an exclusive and an inclusive range and use ranges to slice a string.
let r1 = 1..5; let r2 = 1..=5;
The range 1..5 excludes the upper bound, 1..=5
includes it.
println!("{} {} {}", &word[..3], &word[3..], &word[1..=2]);
One of the bounds can be omitted. The indexes of string slices are byte positions.
$ cargo run -q [1, 2, 3, 4] [1, 2, 3, 4, 5] false true fal con al
Operator precedence
The operator precedence determines which operators are evaluated first. Unary operators bind tighter than binary ones, multiplication and division tighter than addition, and comparisons tighter than the logical operators.
fn main() {
println!("{}", 3 + 5 * 5);
println!("{}", (3 + 5) * 5);
println!("{}", 2 * 3 % 4);
println!("{}", -2_i32.pow(2));
println!("{}", (-2_i32).pow(2));
let x = 3;
println!("{}", x + 2 == 5 && x > 1);
}
The program evaluates several expressions.
println!("{}", 3 + 5 * 5);
println!("{}", (3 + 5) * 5);
Multiplication has a higher precedence than addition. Parentheses change the order of evaluation.
println!("{}", 2 * 3 % 4);
The * and % operators have the same precedence and
are left-associative; the expression is evaluated as (2 * 3) %
4.
println!("{}", -2_i32.pow(2));
println!("{}", (-2_i32).pow(2));
Method calls bind tighter than the unary minus. The first expression is
-(2.pow(2)), which is -4.
$ cargo run -q 28 40 2 -4 4 true
Block expressions
A block in curly braces is an expression; its value is the value of the last
expression in the block, written without a semicolon. The same holds for
if and match.
fn main() {
let x = 10;
let y = 20;
let min = if x < y { x } else { y };
println!("min: {min}");
let area = {
let width = 4;
let height = 3;
width * height
};
println!("area: {area}");
let parity = match x % 2 {
0 => "even",
_ => "odd",
};
println!("{x} is {parity}");
}
The program assigns the values of an if expression, a block, and
a match expression to variables.
let min = if x < y { x } else { y };
The if expression takes the place of the ternary operator of
other languages.
let area = {
let width = 4;
let height = 3;
width * height
};
The block computes a value with the help of temporary variables. The
width and height variables are not visible outside
of the block.
let parity = match x % 2 {
0 => "even",
_ => "odd",
};
The match expression returns the value of the matching arm. See
Rust match expression for more.
$ cargo run -q min: 10 area: 12 10 is even
Source
The Rust Reference: Operator expressions
The Rust Reference: Expression precedence
In this article we have worked with operators and expressions in Rust.
Author
List all Rust tutorials.