0

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
Ákos Vandra-Meyer
  • 1,890
  • 1
  • 23
  • 40

1 Answers1

2

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • Thanks for the input - although the aliasing only happens during return, so I would still say it is a safe operation, although the compiler may not be able to understand that. However your answer also points out that my example was dumbed down too much - In the real scenario the data structure is self-referencing and I am actually returning a subtree of the original one, I'll adjust my question to better reflect this – Ákos Vandra-Meyer Jul 13 '18 at 17:15
  • 1
    @ÁkosVandra editing questions such that they invalidate existing answers is generally frowned upon. – Shepmaster Jul 13 '18 at 17:20
  • 1
    Sorry, reverted the change, and submitted a new question here: https://stackoverflow.com/questions/51330147/how-can-i-pattern-match-a-tuple-containing-a-mut-enum-and-use-the-enum-in-the-m – Ákos Vandra-Meyer Jul 13 '18 at 17:28
  • 1
    @ÁkosVandra and my edit about NLL doesn't happen to address your concern? – Shepmaster Jul 13 '18 at 17:29