6.2Arithmetic operators

Last updated June 12, 2026

You've done arithmetic since chapter 0. This lesson makes the relationship official: the five binary arithmetic operators, the one unary one, and the three places where "it works like math class" stops being the whole truth.

OperatorFormResult
unary minus-xx negated
additionx + ysum
subtractionx - ydifference
multiplicationx * yproduct
divisionx / yquotient
remainderx % ywhat's left after integer division

(C programmers may notice an absence: Rust has no unary +. C's did nothing, so Rust didn't keep it.)

All of these require both operands to be the same type. There are no implicit numeric conversions, as lesson 4.2 established, so i32 + i64 is a type error and as (lesson 4.10) is the explicit bridge. Each operator produces the type it was given: integers in, integers out. That last sentence is where the trouble starts.

Integer division truncates

Lesson 4.5's quiz promised the full story here. Watch the same division performed by two different types:

fn main() {
    println!("{}", 7 / 2);
    println!("{}", 7.0 / 2.0);
}
3
3.5

Float division gives the math-class answer. Integer division answers a different question: how many whole times does it fit? The fractional part isn't rounded; it's discarded. This is called truncation, and it always moves toward zero:

fn main() {
    println!("{}", -7 / 2);
    println!("{}", 7 / -2);
}
-3
-3

Mathematically, -7 ÷ 2 is -3.5; truncation drops the .5 and lands on -3, the neighbor closer to zero. Note that this is not the same as rounding down (that would give -4). Truncation is the right tool surprisingly often: dollars into quarters, minutes into hours, pixels into tiles. When you actually want 3.5, do the division in float country, converting first if needed: 7 as f64 / 2 as f64.

Warning

New programmers often write a formula like celsius * 9 / 5 + 32 with integer literals and then stare at the slightly-wrong answers. One integer division in the middle of a float formula quietly truncates the whole calculation's accuracy. If a formula involves fractions, keep every operand floating-point: 9.0 / 5.0. (Lesson 5.6's quiz already made you dodge this once.)

Division by zero

Integer division by zero has no answer to give, and Rust treats it as seriously as anything in the language. If the compiler can see the zero coming, the program doesn't even build:

fn main() {
    let x = 5 / 0;
    println!("{x}");
}
error: this operation will panic at runtime
 --> src/main.rs:2:13
  |
2 |     let x = 5 / 0;
  |             ^^^^^ attempt to divide `5_i32` by zero
  |
  = note: `#[deny(unconditional_panic)]` on by default

Most divisors aren't visible at compile time, though. Here's a divisor that arrives at runtime, via lesson 5.6's recipe:

use std::io;

fn main() {
    println!("Divide 10 by what?");

    let mut divisor = String::new();
    io::stdin()
        .read_line(&mut divisor)
        .expect("failed to read input");
    let divisor: i32 = divisor.trim().parse().expect("that wasn't a whole number");

    println!("{}", 10 / divisor);
}

Feed it 0 and the program panics, exactly like lesson 3.1's examples:

Divide 10 by what?
0
thread 'main' (2259730) panicked at src/main.rs:12:20:
attempt to divide by zero
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

A panic, not a wrong answer, in debug and release builds alike (unlike the overflow behavior from lesson 4.4, this check is never relaxed). Floats, meanwhile, shrug: 10.0 / 0.0 is inf, per lesson 4.5. Whether a panic or an infinity is the better failure is situational; what Rust won't give you is garbage.

The remainder operator

% is division's sidekick: x % y produces what's left over after x / y truncates.

fn main() {
    println!("{}", 7 % 3);
    println!("{}", 6 % 3);
    println!("{}", 2 % 4);
}
1
0
2

Seven contains two whole threes with 1 left over. Six contains threes exactly, remainder 0. And two contains zero fours, so the whole 2 is left over. That middle case is the famous one: x % y == 0 means x divides evenly by y, which is how lesson 4.6's even-number check worked (n % 2 == 0).

With negative numbers, the remainder takes its sign from the left operand:

fn main() {
    println!("{}", -7 % 3);
    println!("{}", 7 % -3);
}
-1
1

This makes % a true remainder, consistent with truncating division (-7 / 3 is -2, and -2 × 3 + -1 gets you back to -7). Mathematicians' "modulo" behaves differently with negatives, always producing a non-negative result. Keep the distinction in mind whenever the left operand might go negative: checking n % 2 == 1 for oddness fails on negative n (where the remainder is -1); n % 2 != 0 works for both signs.

For advanced readers

When you want the mathematician's version, it ships as a method: (-7_i32).rem_euclid(3) is 2. Wrapping a clock, indexing a circular buffer, normalizing an angle: those are rem_euclid jobs.

Dividing by zero with % panics too, for the same reason and with the message "attempt to calculate the remainder with a divisor of zero".

Exponents: there is no operator

Rust has no exponent operator. Worse, the symbol you might guess exists and means something else:

fn main() {
    println!("{}", 2 ^ 10);
}
8

Warning

^ is bitwise XOR (lesson 6.6), not exponentiation, and 2 ^ 10 compiling quietly to 8 is the classic way to find out. If an exponent calculation is producing absurd numbers, hunt for a ^.

Exponentiation is done with methods. For integers, .pow(), which takes a u32 exponent (a negative exponent would produce a fraction, which an integer can't hold):

fn main() {
    let base: i64 = 7;
    println!("{}", base.pow(12));
}
13841287201

Note the i64: seven to the twelfth is about 13.8 billion, well past i32's ceiling. Integer exponents overflow with spectacular ease, and .pow() plays by lesson 4.4's rules: panic in debug builds, wrap in release. The careful-arithmetic methods from that lesson have a sibling here too (checked_pow).

For floats, two methods: .powi() for whole-number exponents and .powf() for fractional ones.

fn main() {
    let x: f64 = 3.0;
    println!("{}", x.powi(4));
    println!("{}", x.powf(0.5));
}
81
1.7320508075688772

Raising to 0.5 is a square root, showing why powf earns its keep (though .sqrt() exists and says it better).

Quiz time

Question #1

Evaluate by hand, using lesson 6.1's table: 6 + 5 * 4 % 3

Show solution

* and % share the rank above + and group left-to-right between themselves, so this is 6 + ((5 * 4) % 3) = 6 + (20 % 3) = 6 + 2 = 8.

Question #2

What does this print?

fn main() {
    println!("{}", -9 / 2);
    println!("{}", -9 % 2);
}
Show solution
-4
-1

Truncation moves toward zero: -4.5 truncates to -4, not down to -5. The remainder takes the dividend's sign, and the pair stays consistent: -4 × 2 + -1 = -9.

Question #3

A teammate wants two to the eighth power and writes 2 ^ 8. What does their program print, and what should they have written?

Show solution

It prints 10: ^ is XOR, and 0b10 ^ 0b1000 is 0b1010, decimal ten. (The full why is lesson 6.6's; the symptom is enough to diagnose it.) The fix:

fn main() {
    let base: i32 = 2;
    println!("{}", base.pow(8));
}
256

Next lesson: the operators that change a variable in place, and the story of why Rust shipped without ++.