How can I pattern match a tuple containing a &mut

2019-08-25 06:38发布

问题:

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

回答1:

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?