Beginner

Operators in Rust

Learn arithmetic, comparison, logical, bitwise, and range operators in Rust with practical Docker-ready examples

Operators are the verbs of a programming language—the symbols that combine values into new ones. Rust gives you the familiar set of arithmetic, comparison, and logical operators, but its strong, static type system shapes how they behave in ways that may surprise newcomers from dynamic languages.

The defining trait of Rust operators is that they are type-disciplined. You cannot add an integer to a floating-point number without an explicit conversion, integer division truncates rather than producing a float, and there is no implicit coercion to paper over mismatches. The compiler checks every operation, so a program that compiles will not silently corrupt a value through an unexpected type promotion.

Rust also reflects its functional heritage: most “statements” are actually expressions that produce a value. An if block, an arithmetic expression, and a range all evaluate to something you can bind or pass along. In this tutorial you will work through arithmetic, comparison, logical, compound-assignment, bitwise, and range operators, and see how operator precedence resolves complex expressions.

Arithmetic, Comparison, Logical, and Bitwise Operators

The example below exercises the core operator families in a single program. Note that integer division (17 / 5) truncates toward zero, while floating-point division keeps the fractional part. Rust deliberately has no ++ or -- operators—use += 1 instead.

Create a file named operators.rs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
fn main() {
    // --- Arithmetic operators ---
    let a = 17;
    let b = 5;
    println!("Arithmetic:");
    println!("  {} + {} = {}", a, b, a + b);
    println!("  {} - {} = {}", a, b, a - b);
    println!("  {} * {} = {}", a, b, a * b);
    println!("  {} / {} = {}", a, b, a / b); // integer division truncates
    println!("  {} % {} = {}", a, b, a % b); // remainder

    // Floating-point division keeps the fraction
    let x = 17.0;
    let y = 5.0;
    println!("  {} / {} = {}", x, y, x / y);

    // --- Comparison operators (always produce a bool) ---
    println!("Comparison:");
    println!("  {} == {} -> {}", a, b, a == b);
    println!("  {} != {} -> {}", a, b, a != b);
    println!("  {} <  {} -> {}", a, b, a < b);
    println!("  {} >= {} -> {}", a, b, a >= b);

    // --- Logical operators (short-circuiting) ---
    let sunny = true;
    let warm = false;
    println!("Logical:");
    println!("  sunny && warm -> {}", sunny && warm);
    println!("  sunny || warm -> {}", sunny || warm);
    println!("  !sunny        -> {}", !sunny);

    // --- Compound assignment (requires `mut`) ---
    let mut total = 10;
    total += 5;
    total -= 3;
    total *= 2;
    println!("Compound assignment: total = {}", total);

    // --- Bitwise operators ---
    let flags = 0b1100;
    let mask = 0b1010;
    println!("Bitwise:");
    println!("  {:04b} & {:04b} = {:04b}", flags, mask, flags & mask);
    println!("  {:04b} | {:04b} = {:04b}", flags, mask, flags | mask);
    println!("  {:04b} ^ {:04b} = {:04b}", flags, mask, flags ^ mask);
    println!("  1 << 4 = {}", 1 << 4);

    // --- Operator precedence: * and / bind tighter than + and - ---
    println!("Precedence:");
    println!("  2 + 3 * 4   = {}", 2 + 3 * 4);
    println!("  (2 + 3) * 4 = {}", (2 + 3) * 4);

    // --- Range operator (..= is inclusive) ---
    let sum: i32 = (1..=5).sum();
    println!("Range: sum of 1..=5 = {}", sum);
}

A few Rust-specific details worth highlighting:

  • / on integers truncates. 17 / 5 is 3, not 3.4. To get a float, at least one operand must be a float (17.0 / 5.0).
  • Comparison operators always yield a bool. There is no “truthy” coercion—if 1 is a type error in Rust.
  • && and || short-circuit. The right-hand side is only evaluated if needed, which matters when it has side effects.
  • {:04b} is a format specifier: print in binary, zero-padded to 4 digits. The << operator shifts bits left.

String Concatenation

Rust separates the heap-allocated, growable String from the borrowed string slice &str, and this distinction is visible in how concatenation works. The + operator consumes (moves) the left-hand String and borrows the right-hand &str. When you need to keep all the original values, reach for the format! macro instead—it borrows everything and allocates a fresh String.

Create a file named string_ops.rs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
fn main() {
    // `+` moves the left String and appends a &str
    let greeting = String::from("Hello");
    let name = "Rust";
    let message = greeting + ", " + name + "!";
    println!("{}", message);

    // format! borrows its arguments, so they stay usable
    let first = String::from("Code");
    let second = String::from("Archaeology");
    let combined = format!("{}{}", first, second);
    println!("{}", combined);
    println!("Still usable: {} and {}", first, second);

    // repeat() builds a String by repetition
    let line = "=".repeat(10);
    println!("{}", line);
}

Because greeting + ... moves greeting, you could not use greeting again afterward—the compiler would reject it. With format!, both first and second remain valid, which is why the third println! works. This is the ownership system showing up even in something as routine as joining strings.

Running with Docker

Use the official Rust image to compile and run each example without installing a toolchain locally.

1
2
3
4
5
6
7
8
# Pull the official image
docker pull rust:1.83

# Compile and run the operators example
docker run --rm -v $(pwd):/app -w /app rust:1.83 sh -c "rustc operators.rs && ./operators"

# Compile and run the string concatenation example
docker run --rm -v $(pwd):/app -w /app rust:1.83 sh -c "rustc string_ops.rs && ./string_ops"

Expected Output

Running operators.rs:

Arithmetic:
  17 + 5 = 22
  17 - 5 = 12
  17 * 5 = 85
  17 / 5 = 3
  17 % 5 = 2
  17 / 5 = 3.4
Comparison:
  17 == 5 -> false
  17 != 5 -> true
  17 <  5 -> false
  17 >= 5 -> true
Logical:
  sunny && warm -> false
  sunny || warm -> true
  !sunny        -> false
Compound assignment: total = 24
Bitwise:
  1100 & 1010 = 1000
  1100 | 1010 = 1110
  1100 ^ 1010 = 0110
  1 << 4 = 16
Precedence:
  2 + 3 * 4   = 14
  (2 + 3) * 4 = 20
Range: sum of 1..=5 = 15

Running string_ops.rs:

Hello, Rust!
CodeArchaeology
Still usable: Code and Archaeology
==========

Key Concepts

  • Integer division truncates. 17 / 5 is 3; use floating-point operands for a fractional result. The % operator gives the remainder.
  • No ++ or --. Rust has no increment/decrement operators—use compound assignment like count += 1.
  • Compound assignment needs mut. Operators like +=, -=, and *= mutate the binding, so the variable must be declared let mut.
  • Comparisons return bool, never coerced. Rust has no truthiness; conditions must be genuine booleans.
  • Logical && and || short-circuit, evaluating the right operand only when necessary.
  • Bitwise operators (&, |, ^, <<, >>) work on integers and pair well with binary format specifiers like {:04b}.
  • Precedence follows math conventions: *, /, and % bind tighter than + and -; use parentheses to override.
  • Ranges are operators too. 1..5 is exclusive and 1..=5 is inclusive; both produce iterators you can .sum(), loop over, or slice with.

Running Today

All examples can be run using Docker:

docker pull rust:1.83
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining