Operators and Expressions

Assignment Operators

The assignment operator (=) stores a new value into an existing mutable storage location (called a place). Unlike let x = value;—which declares a brand-new variable binding—an assignment expression x = value; overwrites the contents of an already-declared variable, field, array element, or dereferenced pointer.

Declaration (let) vs Reassignment (=)

Syntax Construct Creates New Binding? Requires mut on Existing Variable? Can Change Variable's Type?
let x = 10; let declaration statement Yes (or shadows existing x) No Yes (when shadowing)
x = 20; Assignment expression No (mutates existing storage slot) Yes No (type is fixed at declaration)
fn main() {
    let mut temperature = 20; // Declaration with `let mut`
    println!("Initial: {temperature}");

    temperature = 25;         // Reassignment with `=`
    println!("Updated: {temperature}");
}
Initial: 20
Updated: 25

Place Expressions (Left-Hand Side) vs Value Expressions (Right-Hand Side)

In target = source, the right-hand side (source) can be any expression that produces a value of the matching type, while the left-hand side (target) must be a mutable place expression (also known as an lvalue) that refers to a concrete memory location:

Valid Mutable Place (Left-Hand Side of =) Example What Is Overwritten
Mutable local variable count = 5; The stack slot of count
Mutable struct field point.x = 10.0; The x field inside point
Mutable tuple field pair.0 = "updated"; The first element inside pair
Mutable array or Vec element scores[2] = 99; Element at index 2
Dereferenced mutable reference *counter_ref = 42; The target memory pointed to by counter_ref: &mut i32
Destructuring pattern of places (a, b) = (b, a); Multiple mutable places simultaneously
struct Point {
    x: i32,
    y: i32,
}

fn reset_to_zero(slot: &mut i32) {
    *slot = 0; // Assigns through a dereferenced mutable reference
}

fn main() {
    let mut pt = Point { x: 3, y: 7 };
    let mut items = [10, 20, 30];

    pt.x = 15;       // Struct field assignment
    items[1] = 200;  // Array element assignment
    reset_to_zero(&mut pt.y);

    println!("Point: ({}, {}), items: {items:?}", pt.x, pt.y);
}
Point: (15, 0), items: [10, 200, 30]

Type of an Assignment Expression (())

In C, C++, Java, Python, and JavaScript, an assignment x = 5 evaluates to the assigned value (5). That design allows chained assignments (a = b = 5), but it also enables a notorious class of bugs where a typo if (x = 5) silently overwrites x and tests whether 5 is non-zero.

In Rust, every assignment expression place = value evaluates to the unit value (), not the assigned value!

C / C++:                            Rust:
  (x = 5)  ---> evaluates to 5        (x = 5)  ---> evaluates to ()
fn main() {
    let mut x = 0;

    // The assignment `x = 42` is an expression that produces `()`
    let assignment_result: () = (x = 42);

    println!("x = {x}, assignment_result = {assignment_result:?}");
}
x = 42, assignment_result = ()

Because x = 5 evaluates to () (and if requires a bool), accidentally writing = instead of == inside an if or while condition is caught immediately at compile time:

// ❌ does not compile
fn main() {
    let mut status = 1;

    if status = 2 {
        println!("Matched");
    }
}
error[E0308]: mismatched types
 --> src/main.rs:4:8
  |
4 |     if status = 2 {
  |        ^^^^^^^^^^ expected `bool`, found `()`
  |
help: you might have meant to compare for equality
  |
4 |     if status == 2 {
  |                +

error: aborting due to 1 previous error

Why a = b = 0 Does Not Compile as Expected

Because b = 0 evaluates to (), writing a = b = 0; is parsed as a = (b = 0);, which tries to assign () into a! Instead, write two separate statements or use a destructuring assignment (a, b) = (0, 0);.

Destructuring Assignment

You can assign to multiple existing mutable variables simultaneously using a tuple, array, or struct pattern on the left-hand side of = without using let:

fn main() {
    let mut a = 10;
    let mut b = 99;

    // Swap `a` and `b` in one statement without a temporary variable
    (a, b) = (b, a);
    println!("After swap: a = {a}, b = {b}");

    // Assign first and last elements from an array, ignoring the middle with `..`
    let mut first = 0;
    let mut last = 0;
    [first, .., last] = [100, 200, 300, 400];
    println!("first = {first}, last = {last}");
}
After swap: a = 99, b = 10
first = 100, last = 400

In (a, b) = (b, a);, Rust evaluates the right-hand tuple (b, a) completely before writing the values into a and b.

Memory Behavior: Dropping the Old Value on Reassignment

When you reassign a variable whose type manages resources or heap memory (implements Drop, such as String or Vec<T>), Rust automatically drops the old value stored in that variable before moving the new value into its place:

struct Buffer(&'static str);

impl Drop for Buffer {
    fn drop(&mut self) {
        println!("  [Drop] Freed {}", self.0);
    }
}

fn main() {
    let mut active = Buffer("first buffer");
    println!("About to reassign `active`...");

    // 1. `Buffer("second buffer")` is created
    // 2. Old value `Buffer("first buffer")` is dropped immediately!
    // 3. `active` now holds `Buffer("second buffer")`
    active = Buffer("second buffer");

    println!("After reassignment, active holds: {}", active.0);
}
About to reassign `active`...
  [Drop] Freed first buffer
After reassignment, active holds: second buffer
  [Drop] Freed second buffer

Notice that first buffer is freed immediately on the line active = Buffer("second buffer");, whereas second buffer lives until main exits.

Common Errors

Assigning to an Immutable Binding (E0384)

Every variable or container on the left-hand side of = must be declared with mut:

// ❌ does not compile
fn main() {
    let x = 10;
    x = 20;
}
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:3:5
  |
2 |     let x = 10;
  |         -
  |         |
  |         first assignment to `x`
  |         help: consider making this binding mutable: `mut x`
3 |     x = 20;
  |     ^^^^^^ cannot assign twice to immutable variable

error: aborting due to 1 previous error

Invalid Left-Hand Side of Assignment (E0070)

Expressions that compute a temporary value (such as arithmetic x + 1 or a function call get_value() that returns a value rather than &mut T) are value expressions, not places, and cannot appear on the left-hand side of =:

// ❌ does not compile
fn main() {
    let mut x = 5;
    x + 1 = 10;
}
error[E0070]: invalid left-hand side of assignment
 --> src/main.rs:3:11
  |
3 |     x + 1 = 10;
  |     ----- ^
  |     |
  |     cannot assign to this expression

error: aborting due to 1 previous error