Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I bind a variable in a match arm when matching on a mutable reference?

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(),
        _ => {},
    }
}
like image 796
Shepmaster Avatar asked Apr 17 '15 01:04

Shepmaster


People also ask

Does match borrow Rust?

match "borrows" or "moves", as needed: Rust encourages the developer to think carefully about ownership and borrowing. To ensure that one is not forced to yield ownership of a value prematurely, match is designed with support for merely borrowing substructure (as opposed to always moving such substructure).

How does match work in Rust?

Rust has an extremely powerful control flow construct called match that allows you to compare a value against a series of patterns and then execute code based on which pattern matches.

What are match expressions?

A match expression has a scrutinee expression, which is the value to compare to the patterns. The scrutinee expression and the patterns must have the same type. A match behaves differently depending on whether or not the scrutinee expression is a place expression or value expression.

What is ref in Rust?

ref annotates pattern bindings to make them borrow rather than move. It is not a part of the pattern as far as matching is concerned: it does not affect whether a value is matched, only how it is matched.


1 Answers

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.

like image 163
Chris Morgan Avatar answered Sep 24 '22 03:09

Chris Morgan