7

I am matching on a struct and would like to use a match guard. However, the struct is mutable, and binding variables on the left side of the match arm seems to cause a separate borrow. This then triggers compilation errors, since you can't have a second borrow (mutable or immutable) while the mutable borrow is outstanding.

struct A(u8);

impl A {
    fn is_awesome(&self) -> bool { true }
}

struct Container(A);

impl Container {
    fn update(&mut self) {}

    fn do_a_thing(&mut self) {
        match *self {
            Container(ref a) if a.is_awesome() => self.update(),
            _ => {},
        }
    }
}

fn main() {}
error[E0502]: cannot borrow `*self` as mutable because `self.0` is also borrowed as immutable
  --> src/main.rs:14:51
   |
14 |             Container(ref a) if a.is_awesome() => self.update(),
   |                       -----                       ^^^^ mutable borrow occurs here
   |                       |
   |                       immutable borrow occurs here
15 |             _ => {},
16 |         }
   |         - immutable borrow ends here

My current workaround is to duplicate the logic to compute the match guard before my match, then I can just use the boolean as my match guard. This is not satisfying for the obvious code duplication issues:

fn do_a_thing(&mut self) {
    let awesome = match *self {
        Container(ref a) => a.is_awesome(),
    };

    match *self {
        Container(..) if awesome => self.update(),
        _ => {},
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    I would rewrite `match self { &mut Container(ref a) => … }` as `let a = &self.0; …`, myself. – Chris Morgan Apr 17 '15 at 01:58
  • @ChrisMorgan yeah, so would I. It is a conceit to use pattern matching, without adding an enum or another nested layer, in an attempt to keep the example small. – Shepmaster Apr 17 '15 at 02:00
  • 1
    (Prevailing style is also in such cases `match *self { Container(ref a) => … }`, to take out any references in the match subject rather than the branch patterns.) – Chris Morgan Apr 17 '15 at 02:02
  • @ChrisMorgan good catch, that was leftover from my trials on the playpen! – Shepmaster Apr 17 '15 at 02:04

1 Answers1

5

When non-lexical lifetimes are enabled, your original code works as-is.

Before Non-Lexical Lifetimes

In the name of safety, Rust forbids various classes of things, even though a specific case of them might work. This is one such case, and what you are trying to do is not and never will be possible.

You have created a reference to the contents of self, but then you call self.update() which wants a mutable reference to self. A language could effectively inline update and thus determine that it’s safe to keep that reference alive, but it’s easy to demonstrate that the basic concept won’t always work with this example of badness that the Rust compiler saves you from:

struct A(u8);

struct Container(A);

impl Container {
    fn update(&mut self) {
        self.0 = A(0);
    }

    fn do_a_thing(&mut self) {
        let a = &self.0;
        let before = a.0;
        self.update();
        assert_eq!(before, a.0);
    }
}

fn main() {
    Container(A(1)).do_a_thing();
    // Panic: 1 != 0
}

If this were allowed to compile, it would panic because the target of a, despite its being an immutable reference, changed underneath you, something it clearly must not be allowed to do.

The happy-go-lucky mentality of C++ templates are an example of trying something which might or might not work; it’s quite possible with them that a change deep in the internals of a function might break its users so that they no longer compile. Rust has decided not to go down that path, and so treats each method as a strong isolation barrier. No changes to the body of a function will ever cause code outside the method to stop compiling.

You cannot have any references, mutable or otherwise, to anything inside self while you call a &mut self-requesting method on self.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Chris Morgan
  • 86,207
  • 24
  • 208
  • 215
  • 1
    I’ve had my fair share of disagreements with the Rust compiler on what I should and shouldn’t be allowed to do. Arguing with rustc gets a bit annoying, because I never win the argument. If I think about it long enough, I can *always* find a reason why what I wanted to do was wrong. – Chris Morgan Apr 17 '15 at 02:22
  • I'm not mad at the compiler at all, and I fully agree with the reasoning. In my ideal case, I would have liked a way to bind a variable on the left hand side of the match arm, and somehow have the binding "go out of scope" before the match body starts. That would have allowed me to call my method in the match guard, but still mutate one level up from the bound variable. – Shepmaster Apr 17 '15 at 02:23
  • I don't suppose you have a cleaner solution than my current workaround? – Shepmaster Apr 23 '15 at 13:46
  • No, ’fraid not while we still use lexical borrows. – Chris Morgan Apr 23 '15 at 22:03