How can the code below be made to compile? It seems perfectly safe, but can't convince the compiler that it is.
The version matching *self
gives the error: cannot move out of borrowed content
on the line of the match
The version matching self
gives: use of moved value: *self
enum Foo {
Foo1(u32),
Foo2(i16),
}
impl Foo {
fn bar(&mut self, y: u32) -> (u32, &mut Foo) {
match (*self, y) {
(Foo::Foo1(ref mut a), b) if (b == 5) => {
print!("is five");
*a = b + 42;
(b, self)
}
(Foo::Foo2(ref mut a), b) if (b == 5) => {
print!("is five");
*a = (b + 42) as i16;
(*a * b, self)
}
_ => {
print!("is not five!");
(y, self)
}
}
}
}
I feel like I would need a match arm such as the following, but it doesn't seem to be valid syntax:
(ref mut f @ Foo::Foo1, b) if (b == 5) => {
print!("is five");
f.0 = b + 42;
(b, f)
}
error[E0532]: expected unit struct/variant or constant, found tuple variant `Foo::Foo1`
--> src/main.rs:24:30
|
24 | (ref mut f @ Foo::Foo1, b) if (b == 5) => {
| ^^^^^^^^^ not a unit struct/variant or constant
No, this is not safe. You are attempting to introduce mutable aliasing inside the match arm. The mutable reference a
points into the same value as self
. It would be possible to change self
(e.g. *self = Foo::Foo1(99)
) which would then invalidate a
, so this code is disallowed.
Instead, mutably reborrow self
in the match
statement and have it return the first value of the tuple. Since this value doesn't have a reference to self
, you can then return self
with the result of the match
:
enum Foo {
Foo1(u32),
Foo2(u32), // changed so I don't have to figure out what casting you meant
}
impl Foo {
fn bar(&mut self, y: u32) -> (u32, &mut Foo) {
let next = match (&mut *self, y) {
(Foo::Foo1(a), b @ 5) => {
*a = b + 42;
b
}
(Foo::Foo2(a), b @ 5) => {
*a = b + 42;
*a * b
}
_ => y,
};
(next, self)
}
}
However, returning self
like this is rather pointless here. The caller already has a &mut Foo
, so you don't need to "give it back". This allows simplifying to:
impl Foo {
fn bar(&mut self, y: u32) -> u32 {
match (self, y) {
(Foo::Foo1(a), b @ 5) => {
*a = b + 42;
b
}
(Foo::Foo2(a), b @ 5) => {
*a = b + 42;
*a * b
}
_ => y,
}
}
}
I would still say it is a safe operation, although the compiler may not be able to understand that
With non-lexical lifetimes, the borrow checker becomes more intelligent. Your original code with an added explicit reborrow compiles:
#![feature(nll)]
enum Foo {
Foo1(u32),
Foo2(u32), // changed so I don't have to figure out what casting you meant
}
impl Foo {
fn bar(&mut self, y: u32) -> (u32, &mut Foo) {
match (&mut *self, y) {
(Foo::Foo1(a), b @ 5) => {
*a = b + 42;
(b, self)
}
(Foo::Foo2(a), b @ 5) => {
*a = b + 42;
(*a * b, self)
}
_ => (y, self),
}
}
}
See also:
- Why does matching on a tuple of dereferenced references not work while dereferencing non-tuples does?
- What is the syntax to match on a reference to an enum?
- How can I use match on a pair of borrowed values without copying them?
- Is there any difference between matching on a reference to a pattern or a dereferenced value?