How does ownership interact with pattern matching

Pattern matching moves ownership into bindings by default, but you can borrow data using the ref keyword to retain ownership.

Pattern matching moves by default

You're processing a user command. You have an enum Command. You write a match to handle Command::Delete { file }. Inside the arm, you print the file name. You try to log the original command after the match. The compiler yells. You didn't just read the command; you consumed it. The match took the pieces and left the shell empty.

This is Rust's default behavior. Pattern matching moves ownership of values into the pattern bindings. When you match a value, Rust assumes you want the data. It disassembles the value and hands the pieces to your variables. The original value is gone.

Think of pattern matching as a disassembly line. When you match a value, Rust takes the value apart and hands the parts to the variables in your pattern. The original value is destroyed in the process. You get the parts, but the whole is gone. This prevents accidental aliasing. If the match owns the data, no one else can touch it.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
}

fn main() {
    // Create an owned Message value.
    let msg = Message::Move { x: 10, y: 20 };

    // match moves the value into the bindings by default.
    // The compiler generates code to move 'msg' into the arm,
    // extract the fields, and bind them to 'x' and 'y'.
    match msg {
        Message::Move { x, y } => {
            // 'x' and 'y' own the i32 values.
            // 'msg' has been moved and is invalid.
            println!("Moving to ({}, {})", x, y);
        }
        _ => {}
    }

    // E0382: use of moved value.
    // The compiler rejects this because 'msg' was moved into the match.
    // println!("{:?}", msg);
}

The compiler rejects the second use of msg with E0382 (use of moved value). The error is precise. The value moved. It's not there anymore.

Don't fight the move. If you need the value after the match, you must borrow it.

The Copy exception

The move behavior depends on the type. If the type implements the Copy trait, the match copies the value instead of moving it. i32, bool, char, and tuples of Copy types implement Copy. Matching on a Copy type leaves the original value intact.

#[derive(Copy, Clone)]
enum Status {
    Active,
    Inactive,
}

fn main() {
    // Status implements Copy because all its variants are unit variants.
    let status = Status::Active;

    // match copies the value because Status is Copy.
    // 's' gets a copy of the enum.
    // 'status' remains valid.
    match status {
        Status::Active => println!("System is active"),
        Status::Inactive => println!("System is inactive"),
    }

    // 'status' is still usable.
    // No E0382 error.
    println!("Status after match: {:?}", status);
}

If your enum contains non-Copy fields like String, the enum does not implement Copy. Matching moves the enum. The Copy trait is a signal to the compiler that bitwise copying is safe. If the type isn't Copy, the compiler assumes moving is required to maintain safety.

Check the Copy trait before assuming a match will preserve your value. If the type holds heap data, it's likely not Copy.

Borrowing with ref

To access data without taking ownership, use the ref keyword. ref tells the compiler to bind a reference to the field rather than moving the field. The original value stays alive.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
}

fn main() {
    let msg = Message::Move { x: 10, y: 20 };

    // 'ref' creates a reference binding.
    // 'x' and 'y' become &i32.
    // 'msg' is borrowed, not moved.
    match msg {
        Message::Move { ref x, ref y } => {
            // 'x' and 'y' are references.
            // We can read them without owning the data.
            println!("Borrowed coordinates: ({}, {})", x, y);
        }
        _ => {}
    }

    // 'msg' is still valid.
    // The borrow ended when the match arm finished.
    println!("Message after match: {:?}", msg);
}

ref creates an immutable reference. If you need mutable access, use ref mut. This creates a &mut T binding. You can modify the field through the reference.

struct Config {
    volume: i32,
}

fn main() {
    let mut config = Config { volume: 50 };

    // 'ref mut' creates a mutable reference binding.
    // 'vol' becomes &mut i32.
    // We can modify the field through 'vol'.
    match config {
        Config { ref mut volume } => {
            *volume += 10;
        }
    }

    // The mutation is visible.
    println!("Volume: {}", config.volume);
}

Use ref mut when you need to mutate a field inside a match without moving the whole struct. The mutable borrow enforces exclusive access. The compiler ensures no other references exist while the mutation happens.

The modern convention: match on references

The community prefers matching on a reference rather than using ref in the pattern. Writing &value at the start of the match makes the borrowing intent obvious. It also avoids the confusion of ref in nested patterns.

enum Message {
    Quit,
    Move { x: i32, y: i32 },
}

fn main() {
    let msg = Message::Move { x: 10, y: 20 };

    // Match on a reference to 'msg'.
    // The pattern binds references automatically.
    // 'x' and 'y' become &i32.
    // This is the preferred style over 'ref x'.
    match &msg {
        Message::Move { x, y } => {
            println!("Borrowed coordinates: ({}, {})", x, y);
        }
        _ => {}
    }

    // 'msg' is still usable.
    println!("Message after match: {:?}", msg);
}

When you match on &msg, the compiler adjusts the pattern bindings to match the reference type. The bindings become references. You get the same result as ref, but the borrowing is explicit at the call site. This keeps the intent visible. Readers see &msg and know immediately that the match borrows.

Convention aside: Rc::clone(&data) vs data.clone() follows the same principle. Explicit forms signal intent. match &value signals borrowing. match value signals moving. Stick to the explicit form. It pays off when debugging.

Prefer &value at the match head. It keeps the borrowing intent visible and avoids hidden reference bindings.

Match ergonomics: auto-deref

Rust supports match ergonomics. If you match on a reference to an Option or Result, you can write the pattern without the &. The compiler inserts the & for you. This reduces boilerplate while preserving safety.

fn main() {
    let opt = Some(5);

    // Match on a reference to the Option.
    // The pattern 'Some(x)' works even though 'opt' is &Option.
    // The compiler auto-dereferences the reference.
    // 'x' becomes &i32.
    match &opt {
        Some(x) => {
            // 'x' is &i32.
            // We can read the value without cloning.
            println!("Value: {}", x);
        }
        None => println!("No value"),
    }
}

Match ergonomics applies to Option, Result, and references. If you match on &Some(5), the pattern Some(x) binds x as &i32. If you match on &&Some(5), x becomes &&i32. The compiler tracks the reference depth. You don't need to write &Some(&x).

This feature simplifies code that works with references. You can write natural patterns and let the compiler handle the reference adjustments. The type of the binding reflects the reference depth.

Trust the borrow checker. It usually has a point. Match ergonomics keeps patterns readable while enforcing reference rules.

Pitfalls: E0507 and moving out of loans

A common error occurs when you match on a reference and try to move a field out. The compiler rejects this with E0507 (cannot move out of borrowed content). You cannot steal data from a loan.

struct Data {
    name: String,
}

fn main() {
    let data = Data {
        name: String::from("Alice"),
    };

    // Match on a reference to 'data'.
    // 'data' is borrowed, not owned.
    match &data {
        Data { name } => {
            // E0507: cannot move out of borrowed content.
            // 'name' would be a String, but we only have &Data.
            // We cannot move the String out of the reference.
            // println!("{}", name);
        }
    }
}

The error E0507 means you're trying to move a value out of a reference. The reference only gives you access, not ownership. You can't take the data and leave the reference pointing to nothing.

Fix this by borrowing the field or cloning it. Use ref or match on a reference to borrow. Use .clone() if you need owned data and cloning is cheap.

struct Data {
    name: String,
}

fn main() {
    let data = Data {
        name: String::from("Alice"),
    };

    // Match on a reference.
    // 'name' becomes &String.
    // We borrow the field.
    match &data {
        Data { name } => {
            println!("Name: {}", name);
        }
    }

    // Or clone if you need ownership.
    match &data {
        Data { name } => {
            let owned_name = name.clone();
            println!("Owned name: {}", owned_name);
        }
    }
}

E0507 means you're trying to steal from a loan. Clone or borrow. The compiler protects the integrity of references.

Decision: when to use this vs alternatives

Use match value when you own the value and the match arms need full ownership of the extracted fields. Use match &value when you only have a reference or you need to keep the original value alive after the match. Use ref bindings when you are matching a value directly but need to borrow specific fields, though modern style prefers matching on a reference instead. Use match value.clone() when you must extract owned data but cannot give up the original value and cloning is cheap.

Use match &mut value when you need mutable access to the value and want to borrow fields mutably. Use ref mut when you need to mutate a field inside a match without moving the whole struct. Use Copy types when you want matches to preserve the original value automatically.

The compiler doesn't guess. If you want a reference, ask for one. If you want ownership, take it. The pattern tells the compiler what you need.

Where to go next