1

I need to match on an optional value that is on self, from a method, and based on that match, call a method on self that takes self mutably. I'm trying to do a pretty high-performance game, so as much as I'd like to dispense with mutability, I can't in this situation: the methods really do need to access the struct mutably, and they really do need to be dispatched based on the structs properties. Here is an MVCE:

enum Baz {
    A,
    B,
    C,
}
struct Foo {
    bar: Option<Baz>,
}

impl Foo {
    pub fn dostuff(&mut self, fizz: i32) {
        if let Some(ref b) = self.bar {
            match b {
                &Baz::A => self.do_a(fizz),
                &Baz::B => self.do_b(fizz + 2),
                &Baz::C => self.do_c(fizz + 1),
            }
        }
    }

    pub fn do_a(&mut self, f: i32) {
        println!("A, with fizz {}", f);
    }
    pub fn do_b(&mut self, f: i32) {
        println!("B, with fizz {}", f);
    }
    pub fn do_c(&mut self, f: i32) {
        println!("C, with fizz {}", f);
    }
}

fn main() {
    let foo = Foo { bar: Some(Baz::A) };
    foo.dostuff(3);
}

And here is the playground.

error[E0502]: cannot borrow `*self` as mutable because `self.bar.0` is also borrowed as immutable
  --> src/main.rs:14:28
   |
12 |         if let Some(ref b) = self.bar {
   |                     ----- immutable borrow occurs here
13 |             match b {
14 |                 &Baz::A => self.do_a(fizz),
   |                            ^^^^ mutable borrow occurs here
...
18 |         }
   |         - immutable borrow ends here

I thought I'd made my peace with the borrow checker, but apparently not: I have no idea how to fix this, although I do know why this is happening. I would appreciate it if somebody explained how to avoid this in the future.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Alexis Dumas
  • 1,299
  • 11
  • 30
  • 1
    "after accessing property" — not just *accessing*, but keeping a reference to it. If `do_a` / `do_b` / `do_c` were to change `self.b`, that would violate Rust's memory safety rules because an immutable reference would have changed. – Shepmaster Aug 09 '17 at 12:43

2 Answers2

2
  1. You can unwrap the Option(Baz) within the match instead of using if let..
  2. Declare foo as mutable

Here is the code ...

enum Baz {
    A,
    B,
    C,
}
struct Foo {
    bar: Option<Baz>,
}

impl Foo {
    pub fn dostuff(&mut self, fizz: i32) {
        match self.bar {
            Some(Baz::A) => self.do_a(fizz),
            Some(Baz::B) => self.do_b(fizz + 2),
            Some(Baz::C) => self.do_c(fizz + 1),
            None => {},
        }
    }

    pub fn do_a(&mut self, f: i32) {
        println!("A, with fizz {}", f);
    }
    pub fn do_b(&mut self, f: i32) {
        println!("B, with fizz {}", f);
    }
    pub fn do_c(&mut self, f: i32) {
        println!("C, with fizz {}", f);
    }
}

fn main() {
    let mut foo = Foo { bar: Some(Baz::B) };
    foo.dostuff(3);
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Malice
  • 1,457
  • 16
  • 32
2

If you care about the references, as far as I know, Rust's rules forbid you from having such code. You have to either:

  1. Copy or Clone your bar object and unwrap as how as you want.
  2. Add some flag and make the code not have multiple references to the object. For example, add two variables which will tell you to what to do. You match your enum, set these variables and then you match these variables. This is less-useful than the first item here but this is a solution also.

  3. Be more functional-like:

    enum Baz {
        A,
        B,
        C,
    }
    struct Foo {
        bar: Option<Baz>,
    }
    
    impl Foo {
        pub fn dostuff(&mut self, fizz: i32) {
            let (method, arg) = match self.bar {
                Some(ref b) => {
                    match b {
                        &Baz::A => (Self::do_a as fn(&mut Self, i32)>, fizz),
                        &Baz::B => (Self::do_b as fn(&mut Self, i32)>, fizz + 2),
                        &Baz::C => (Self::do_c as fn(&mut Self, i32)>, fizz + 1),
                    }
                },
                None => return,
            };
            method(self, arg);
        }
    
        pub fn do_a(&mut self, f: i32) {
            println!("A, with fizz {}", f);
        }
        pub fn do_b(&mut self, f: i32) {
            println!("B, with fizz {}", f);
        }
        pub fn do_c(&mut self, f: i32) {
            println!("C, with fizz {}", f);
        }
    }
    
    fn main() {
        let mut foo = Foo { bar: Some(Baz::A) };
        foo.dostuff(3);
    }
    

    By doing so you return a method which you wanna call with it's argument, so you just call what you need to. I am pretty sure this solution can be rewritten without using Box but just references to a method but I don't know how.

VP.
  • 15,509
  • 17
  • 91
  • 161
  • 1
    I think I'll go with the third option! I tried cloning the option but for some reason it didn't help, and I also tried matching the option like you said and it didn't work, but based on what I know of the borrow checkers rules I think the last one will work. Thanks! – Alexis Dumas Aug 09 '17 at 13:56
  • @ChristopherDumas I have edited the code so that it does not use `Box` anymore but raw function pointers. – VP. Aug 21 '17 at 13:43