I am match
ing 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(),
_ => {},
}
}
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).
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.
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.
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.
When non-lexical lifetimes are enabled, your original code works as-is.
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
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With