Operators are the building blocks of expressions in any programming language. Rust's operator set is largely familiar to developers from C, C++, or Java — with a few notable differences that reflect Rust's focus on explicitness and safety.
Arithmetic Operators
Rust supports the standard arithmetic operators for numeric types:
| Operator | Operation | Example |
|---|---|---|
+ | Addition | 2 + 3 → 5 |
- | Subtraction | 5 - 2 → 3 |
* | Multiplication | 4 * 3 → 12 |
/ | Division | 10 / 3 → 3 (integer) |
% | Remainder (modulo) | 13 % 3 → 1 |
fn operators() {
let mut a = 2 + 3 * 4; // Follows standard precedence: * before +
println!("{}", a); // 14
// No ++ or -- operators in Rust. Use += 1 instead.
a += 1; // a = 15
a -= 1; // a = 14
a *= 2; // a = 28
a /= 2; // a = 14
a %= 3; // a = 2
println!("remainder of 13 / 3 = {}", 13 % 3); // 1
}
No `++` or `--` Operators
Rust deliberately does not have the ++ (increment) and -- (decrement) operators found in C, C++, and Java. Instead, you must use explicit compound assignment:
let mut x = 5;
x += 1; // increment
x -= 1; // decrement
This avoids ambiguity between pre-increment and post-increment (++x vs x++), a common source of subtle bugs in C/C++.
Integer Division and Remainder
Integer division in Rust truncates toward zero, consistent with most languages:
println!("{}", 7 / 2); // 3 (truncated, not 3.5)
println!("{}", -7 / 2); // -3 (truncates toward zero, not -4)
println!("{}", 7 % 2); // 1
println!("{}", -7 % 2); // -1 (remainder has the sign of the dividend)
Exponentiation
Rust has no ** operator for exponentiation. Instead, it provides methods on numeric types:
fn power_examples() {
let mut a: i32 = 5;
// Integer exponentiation
let a_cubed = i32::pow(a, 3); // a^3
println!("a cubed = {}", a_cubed); // 125
// Float exponentiation
let b = 2.5f64;
let b_cubed = f64::powi(b, 3); // integer exponent
let b_to_pi = f64::powf(b, std::f64::consts::PI); // float exponent
println!("{} cubed = {}", b, b_cubed); // 2.5 cubed = 15.625
println!("{}^pi = {}", b, b_to_pi); // 2.5^pi = 17.789...
}
| Method | Type | Description |
|---|---|---|
i32::pow(base, exp) | Integer | Integer base, integer exponent |
f64::powi(base, exp) | Float | Float base, integer exponent (faster) |
f64::powf(base, exp) | Float | Float base, float exponent |
Compound Assignment Operators
Rust supports the full set of compound assignment operators:
| Operator | Equivalent | Example |
|---|---|---|
+= | a = a + b | a += 5 |
-= | a = a - b | a -= 5 |
*= | a = a * b | a *= 2 |
/= | a = a / b | a /= 2 |
%= | a = a % b | a %= 3 |
Bitwise Operators
Bitwise operators work on the individual bits of integer values. They are only available on integer types in Rust:
| Operator | Operation | Example | ||
|---|---|---|---|---|
| `\ | ` | Bitwise OR | `1 \ | 2 → 3` |
& | Bitwise AND | 5 & 3 → 1 | ||
^ | Bitwise XOR | 5 ^ 3 → 6 | ||
! | Bitwise NOT | !5 (inverts all bits) | ||
<< | Left shift | 1 << 10 → 1024 | ||
>> | Right shift | 1024 >> 10 → 1 |
fn bitwise_examples() {
// OR: 01 | 10 = 11 = 3 in decimal
let c = 1 | 2;
println!("1 | 2 = {}", c); // 3
// AND: 0101 & 0011 = 0001 = 1
let d = 5 & 3;
println!("5 & 3 = {}", d); // 1
// XOR: 0101 ^ 0011 = 0110 = 6
let e = 5 ^ 3;
println!("5 ^ 3 = {}", e); // 6
// Left shift: multiply by 2^n
let two_to_power_10 = 1 << 10;
println!("2^10 = {}", two_to_power_10); // 1024
// Right shift: divide by 2^n
let half = 1024u32 >> 1;
println!("1024 >> 1 = {}", half); // 512
}
Bit Shifting Visualization
Left shift (<<):
1 = 0000 0001
1 << 3 = 0000 1000 = 8
Right shift (>>):
8 = 0000 1000
8 >> 3 = 0000 0001 = 1
Bitwise compound assignments also work: |=, &=, ^=, <<=, >>=.
Logical Operators
Logical operators work on boolean values and produce boolean results:
| Operator | Operation | Notes | ||
|---|---|---|---|---|
&& | Logical AND | Short-circuits: if left is false, right not evaluated | ||
| `\ | \ | ` | Logical OR | Short-circuits: if left is true, right not evaluated |
! | Logical NOT | Negates a boolean |
fn logical_examples() {
let x = true;
let y = false;
println!("x && y = {}", x && y); // false
println!("x || y = {}", x || y); // true
println!("!x = {}", !x); // false
// Short-circuit evaluation
let a = 5;
let b = 0;
if b != 0 && a / b > 2 { // a / b is never evaluated because b != 0 is false
println!("quotient > 2");
}
}
Comparison Operators
Comparison operators evaluate to boolean values:
| Operator | Meaning | Example |
|---|---|---|
== | Equal to | 5 == 5 → true |
!= | Not equal to | 5 != 4 → true |
< | Less than | 3 < 5 → true |
<= | Less than or equal | 5 <= 5 → true |
> | Greater than | 7 > 3 → true |
>= | Greater than or equal | 3 >= 3 → true |
fn comparison_examples() {
use std::f64::consts::PI;
let pi_less_4 = PI < 4.0;
println!("pi < 4.0: {}", pi_less_4); // true
let x = 5;
let x_is_5 = x == 5;
println!("x == 5: {}", x_is_5); // true
// Comparison can be stored in variables
let a = 10;
let b = 20;
let a_lt_b: bool = a < b;
println!("a < b: {}", a_lt_b); // true
}
Operator Precedence
Rust follows standard mathematical operator precedence. From highest to lowest:
Precedence (highest to lowest):
1. Unary: ! - * &
2. Power: (no built-in ** operator)
3. Multiply: * / %
4. Add: + -
5. Shift: << >>
6. Bitwise: & ^ |
7. Compare: == != < <= > >=
8. Logical: &&
9. Logical: ||
10. Assignment: = += -= etc.
let result = 2 + 3 * 4; // 14, not 20 (multiplication before addition)
let result = (2 + 3) * 4; // 20 (parentheses override precedence)
When in doubt, use parentheses to make intent explicit. This is especially important in complex boolean expressions.
Type Compatibility and Casting
Rust does not perform implicit type coercion between numeric types. You must use the as keyword to cast between types:
let x: i32 = 100;
let y: f64 = x as f64; // explicit cast required
let z: u8 = 255i32 as u8; // truncating cast — use carefully
println!("{} as f64 = {}", x, y); // 100 as f64 = 100
println!("300 as u8 = {}", 300i32 as u8); // 44 (wraps: 300 - 256 = 44)
Unlike C, truncating casts are defined behavior in Rust (they wrap modulo 2^N), but they can still produce unexpected values. Use checked arithmetic methods when you need to detect truncation.
Complete Example
fn main() {
// Arithmetic
let mut a = 2 + 3 * 4;
println!("2 + 3 * 4 = {}", a); // 14
a += 1;
println!("after += 1: {}", a); // 15
println!("13 % 3 = {}", 13 % 3); // 1
// Exponentiation
let a_cubed = i32::pow(a, 3);
println!("{} cubed = {}", a, a_cubed);
// Float power
let b = 2.5f64;
let b_cubed = f64::powi(b, 3);
let b_to_pi = f64::powf(b, std::f64::consts::PI);
println!("{} cubed = {}, {}^pi = {}", b, b_cubed, b, b_to_pi);
// Bitwise
let c = 1 | 2;
println!("1 | 2 = {}", c); // 3
let two_to_power_10 = 1 << 10;
println!("2^10 = {}", two_to_power_10); // 1024
// Comparison and logical
let pi_less_4 = std::f64::consts::PI < 4.0;
println!("pi < 4.0: {}", pi_less_4); // true
let x = 5;
let x_is_5 = x == 5;
println!("x == 5: {}", x_is_5); // true
}
Conclusion
Rust's operators are deliberately straightforward. The absence of ++ and -- removes subtle bugs. The requirement for explicit casts prevents silent data loss. The lack of an exponentiation operator (**) is replaced by clear method calls that distinguish integer from float exponentiation.
Combined with Rust's static type system, operators in Rust give you the expressive power of any systems language while eliminating the class of bugs that come from implicit conversions and undefined behavior.