3

I'd like to change my_id, only if present, to another value:

fn read(id: &mut i32) {
    *id = 42;
}

struct S {
    my_id: Option<i32>,
}

impl S {
    fn run(&mut self) {
        match self.my_id {
            Some(mut id) => read(&mut id),
            _ => (),
        }
    }
}

fn main() {
    let mut s = S { my_id: 0.into() };

    s.run();

    println!("{:?}", s.my_id);
}

playground

This code prints Some(0), which means the substitution failed, but I don't understand why. Am I losing the mutability because of pattern matching?

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
nyarlathotep108
  • 5,275
  • 2
  • 26
  • 64

3 Answers3

7

Shepmaster's answer and mcarton's answer gave great explanations how your i32 is being copied instead of referenced in the pattern match.

I'd like to add that the ref keyword exists specifically to handle cases like this, where you want a reference and not a copy inside a pattern match:

fn read(id: &mut i32) {
    *id = 42;
}

struct S {
    my_id: Option<i32>,
}

impl S {
    fn run(&mut self) {
        match self.my_id {
            // `ref` keyword added here
            Some(ref mut id) => read(id),
            _ => (),
        }
    }
}

fn main() {
    let mut s = S { my_id: 0.into() };
    s.run();
    println!("{:?}", s.my_id); // prints "Some(42)"
}

playground

See also:

pretzelhammer
  • 13,874
  • 15
  • 47
  • 98
5

The problem becomes obvious when you replace the type of my_id by a non-Copy type:

fn read(_: &mut String) {}

struct S {
    my_id: Option<String>,
}

impl S {
    fn run(&mut self) {
        match self.my_id {
            Some(mut id) => read(&mut id),
            _ => (),
        }
    }
}
error[E0507]: cannot move out of `self.my_id.0` which is behind a mutable reference
  --> src/lib.rs:9:15
   |
9  |         match self.my_id {
   |               ^^^^^^^^^^ help: consider borrowing here: `&self.my_id`
10 |             Some(mut id) => read(&mut id),
   |                  ------
   |                  |
   |                  data moved here
   |                  move occurs because `id` has type `std::string::String`, which does not implement the `Copy` trait

Indeed Some(mut id) does not match by reference: You've just made a copy of the field. What you really wanted was to match on &mut self.my_id, which doesn't require mut in the pattern:

match &mut self.my_id {
    Some(id) => read(id),
    _ => (),
}
mcarton
  • 27,633
  • 5
  • 85
  • 95
3

Yes and no. By using Some(mut id), you are stating that id should be an i32. Because i32 implements Copy, a mutable copy of the value stored in self is made, then modified by read. At no point is the value inside self modified.

The direct fix is to take a reference:

match &mut self.my_id {
    Some(id) => read(id),
    _ => (),
}

This is more idiomatically written with an if let:

if let Some(id) = &mut self.my_id {
    read(id);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366