Arithmetic Operators
Arithmetic operators perform mathematical operations on numeric values. Rust has five binary arithmetic operators and one unary operator. Each one is an expression that produces a new value.
| Operator | Name | Trait | Example | Result |
|---|---|---|---|---|
+ |
Addition | Add |
17 + 5 |
22 |
- |
Subtraction | Sub |
17 - 5 |
12 |
* |
Multiplication | Mul |
17 * 5 |
85 |
/ |
Division | Div |
17 / 5 |
3 |
% |
Remainder | Rem |
17 % 5 |
2 |
- (unary) |
Negation | Neg |
-17 |
-17 |
fn main() {
let a = 17;
let b = 5;
println!("{} {} {} {} {}", a + b, a - b, a * b, a / b, a % b);
}
22 12 85 3 2
Every arithmetic operator is defined by a trait in std::ops. a + b is another way of writing Add::add(a, b). The compiler knows the built-in behavior for the primitive numeric types, and user-defined types get operators by implementing the traits.
Both Operands Must Have the Same Type
Rust never converts numeric types implicitly. For a + b with primitive numbers, both operands must be the same type, and the result has that type.
// ❌ does not compile
fn main() {
let a: i32 = 5;
let b: i64 = 10;
let c = a + b;
println!("{c}");
}
error[E0308]: mismatched types
--> e1.rs:5:17
|
5 | let c = a + b;
| ^ expected `i32`, found `i64`
error[E0277]: cannot add `i64` to `i32`
--> e1.rs:5:15
|
5 | let c = a + b;
| ^ no implementation for `i32 + i64`
|
= help: the trait `Add<i64>` is not implemented for `i32`
Mixing integers and floats fails for the same reason:
// ❌ does not compile
fn main() {
let a: i32 = 5;
let b: f64 = 2.0;
println!("{}", a * b);
}
error[E0277]: cannot multiply `i32` by `f64`
--> e2.rs:5:22
|
5 | println!("{}", a * b);
| ^ no implementation for `i32 * f64`
|
= help: the trait `Mul<f64>` is not implemented for `i32`
The fix is an explicit conversion with as or with From/Into (see Casting Between Types):
fn main() {
let total: i32 = 7;
let count: i32 = 2;
println!("{}", total / count);
println!("{}", total as f64 / count as f64);
}
3
3.5
Unsuffixed literals adapt to the other operand. In x + 1 where x is u8, the literal 1 is inferred to be u8. A literal can also carry its type as a suffix: 3_000_000_000u32.
Integer Arithmetic
Integer operators work on whole numbers and produce whole numbers.
Division Truncates Toward Zero
Integer division discards the fractional part. It does not round down or to the nearest value. It truncates toward zero, so negative results round up toward zero.
fn main() {
println!("{} {} {} {}", 7 / 2, -7 / 2, 7 / -2, -7 / -2);
}
3 -3 -3 3
-7 / 2 is -3, not -4.
Remainder Takes the Sign of the Left Operand
% is the remainder operator, not the mathematical modulo. It satisfies this identity for integers, as long as no overflow occurs:
a == (a / b) * b + a % b
Because / truncates toward zero, the remainder has the same sign as the left operand a. The sign of the right operand has no effect.
fn main() {
println!("{} {} {} {}", 7 % 3, -7 % 3, 7 % -3, -7 % -3);
let (x, y) = (-7, 3);
println!("{}", (x / y) * y + x % y);
}
1 -1 1 -1
-7
| Expression | / |
% |
|---|---|---|
7 / 3, 7 % 3 |
2 |
1 |
-7 / 3, -7 % 3 |
-2 |
-1 |
7 / -3, 7 % -3 |
-2 |
1 |
-7 / -3, -7 % -3 |
2 |
-1 |
Euclidean Division
A remainder that is always zero or positive is needed for tasks such as wrapping an index around a buffer. Integer types provide div_euclid and rem_euclid for this. With rem_euclid, the result is never negative for a nonzero divisor.
fn main() {
println!("{} {}", (-7i32).div_euclid(3), (-7i32).rem_euclid(3));
println!("{} {}", 7i32.div_euclid(-3), 7i32.rem_euclid(-3));
println!("{} {}", (-7i32).div_euclid(-3), (-7i32).rem_euclid(-3));
}
-3 2
-2 1
3 2
-7 % 3 is -1, while (-7i32).rem_euclid(3) is 2. Use rem_euclid when the result must be a valid index or a position on a clock face.
Panics in Integer Arithmetic
Two integer operations can panic for every build profile:
| Operation | Panic message |
|---|---|
| Division or remainder by zero | attempt to divide by zero / attempt to calculate the remainder with a divisor of zero |
MIN / -1 or MIN % -1 for a signed type |
attempt to divide with overflow / attempt to calculate the remainder with overflow |
i32::MIN / -1 would be 2147483648, which does not fit in i32.
fn main() {
let args: Vec<String> = std::env::args().collect();
let zero = args.len() as i32 - 1; // 0 at runtime
let min = i32::MIN + zero;
let minus_one = -1 + zero;
println!("{}", min / minus_one);
}
thread 'main' panicked at src/main.rs:6:20:
attempt to divide with overflow
+, -, * and unary - can also overflow. Their behavior differs between debug and release builds and is described in Overflow and Division by Zero.
Floating-Point Arithmetic
For f32 and f64, the operators follow the IEEE 754 standard. Division is real division, and % is defined too. Floating-point operations never panic.
fn main() {
println!("{} {} {}", 7.0 / 2.0, -7.5 % 2.0, 5.5 % 2.0);
println!("{}", 0.1 + 0.2);
}
3.5 -1.5 1.5
0.30000000000000004
Float % also takes the sign of the left operand. 0.1 + 0.2 is not exactly 0.3 because neither value is exactly representable in binary.
Operations with no finite result produce special values instead of failing:
fn main() {
println!("{} {} {}", 1.0 / 0.0, -1.0 / 0.0, 0.0 / 0.0);
println!("{}", 1.0_f64 % 0.0);
}
inf -inf NaN
NaN
| Expression | Result |
|---|---|
1.0 / 0.0 |
inf |
-1.0 / 0.0 |
-inf |
0.0 / 0.0 |
NaN |
x % 0.0 |
NaN |
Unary Negation
Unary - flips the sign of a signed integer or a float. It is not defined for unsigned types:
// ❌ does not compile
fn main() {
let n: u32 = 5;
let m = -n;
println!("{m}");
}
error[E0600]: cannot apply unary operator `-` to type `u32`
--> e3.rs:4:13
|
4 | let m = -n;
| ^^ cannot apply unary operator `-`
|
= note: unsigned values cannot be negated
Negating the minimum value of a signed type overflows, because -i32::MIN does not fit in i32.
Unary - binds more loosely than a method call, so -2i32.pow(2) is -(2i32.pow(2)):
fn main() {
println!("{}", -2i32.pow(2));
println!("{}", (-2i32).pow(2));
}
-4
4
Precedence and Associativity
*, / and % have the same precedence, and it is higher than that of + and -. All binary arithmetic operators are left-associative, so operators of equal precedence are evaluated from left to right. Parentheses override both rules.
fn main() {
println!("{}", 2 + 3 * 4);
println!("{}", (2 + 3) * 4);
println!("{}", 100 - 30 - 20);
println!("{}", 100 / 5 / 2);
println!("{}", 10 % 4 * 3);
}
14
20
50
10
6
100 - 30 - 20 is (100 - 30) - 20, not 100 - (30 - 20). 10 % 4 * 3 is (10 % 4) * 3.
Arithmetic on References
The standard library implements the arithmetic traits for references to numbers, so &a + &b, &a + b and a + &b all work and produce an owned value. This matters when iterating, because iterators often yield &i32.
fn main() {
let a = 10;
let b = 4;
let (ra, rb) = (&a, &b);
println!("{} {} {}", ra + rb, ra * 2, a - rb);
}
14 20 6
Strings
+ is also implemented for strings, with a specific shape: String + &str produces a String. The left operand is moved and consumed. Two &str values cannot be added.
fn main() {
let hello = String::from("Hello, ");
let world = "world";
let greeting = hello + world + "!";
println!("{greeting}");
}
Hello, world!
// ❌ does not compile
fn main() {
let a = "Hello, ";
let b = "world";
let c = a + b;
println!("{c}");
}
error[E0369]: cannot add `&str` to `&str`
--> e4.rs:5:15
|
5 | let c = a + b;
| - ^ - &str
| | |
| | `+` cannot be used to concatenate two `&str` strings
| &str
|
= note: string concatenation requires an owned `String` on the left
help: create an owned `String` from a string reference
|
5 | let c = a.to_owned() + b;
| +++++++++++
For more than a couple of pieces, format! is usually clearer than a chain of +.
Implementing Arithmetic Operators for Your Own Types
An operator is available on a type when the type implements the matching trait. The associated type Output is the type of the result. The right-hand type is the trait's type parameter and defaults to Self.
use std::ops::{Add, Mul, Neg, Sub};
#[derive(Debug, Clone, Copy, PartialEq)]
struct Vec2 {
x: f64,
y: f64,
}
impl Add for Vec2 {
type Output = Vec2;
fn add(self, rhs: Vec2) -> Vec2 {
Vec2 { x: self.x + rhs.x, y: self.y + rhs.y }
}
}
impl Sub for Vec2 {
type Output = Vec2;
fn sub(self, rhs: Vec2) -> Vec2 {
Vec2 { x: self.x - rhs.x, y: self.y - rhs.y }
}
}
impl Mul<f64> for Vec2 {
type Output = Vec2;
fn mul(self, k: f64) -> Vec2 {
Vec2 { x: self.x * k, y: self.y * k }
}
}
impl Neg for Vec2 {
type Output = Vec2;
fn neg(self) -> Vec2 {
Vec2 { x: -self.x, y: -self.y }
}
}
fn main() {
let p = Vec2 { x: 1.0, y: 2.0 };
let q = Vec2 { x: 3.0, y: 5.0 };
println!("{:?}", p + q);
println!("{:?}", q - p);
println!("{:?}", p * 3.0);
println!("{:?}", -p);
}
Vec2 { x: 4.0, y: 7.0 }
Vec2 { x: 2.0, y: 3.0 }
Vec2 { x: 3.0, y: 6.0 }
Vec2 { x: -1.0, y: -2.0 }
Mul<f64> for Vec2 allows p * 3.0 but not 3.0 * p. The reverse needs its own impl Mul<Vec2> for f64.
Method Alternatives to the Operators
When the operator's default behavior on overflow is not what the code needs, integer types provide named methods that make the choice explicit.
| Family | Behavior on overflow | Example | Result |
|---|---|---|---|
checked_* |
Returns None |
i32::MAX.checked_add(1) |
None |
wrapping_* |
Wraps around | i32::MAX.wrapping_add(1) |
-2147483648 |
saturating_* |
Clamps to MIN/MAX |
i32::MAX.saturating_add(1) |
2147483647 |
overflowing_* |
Returns (value, overflowed) |
i32::MAX.overflowing_add(1) |
(-2147483648, true) |
fn main() {
println!("{:?}", i32::MAX.checked_add(1));
println!("{:?}", 5i32.checked_div(0));
println!("{}", i32::MAX.wrapping_add(1));
}
None
None
-2147483648
checked_div returns None for division by zero as well as for MIN / -1.