Search code examples
referencerustmutable

Is there a way of extracting a value from a &mut enum when the old one is discarded?


I would like to extract a value from a pattern matched mutable reference and discard the old one.

This is a minimal example I've come up with:

fn main() {
    #[derive(Debug)]
    enum D<A> {
        E1(A),
        E2(A, A),
    };

    trait Zero<A> {
        fn zero() -> A;
    }

    impl<A> D<A> {
        pub fn push2(&mut self, e: A) {
            match self {
                D::E1(e1) => {
                    *self = D::E2(*e1, e);
                }
                _ => unimplemented!(),
            }
        }

        pub fn push(&mut self, e: A)
        where
            A: Zero<A>,
        {
            match self {
                D::E1(e1) => {
                    let mut r = A::zero();
                    std::mem::swap(&mut r, e1);
                    *self = D::E2(e, r);
                }
                _ => unimplemented!(),
            };
        }
    }

    impl Zero<i32> for i32 {
        fn zero() -> i32 {
            0
        }
    }

    let mut d = D::E1(10);
    d.push(11);

    println!("{:?}", d);
}

playground

push2 fails with:

error[E0507]: cannot move out of borrowed content
  --> src/main.rs:17:39
   |
17 |                         *self = D::E2(*e1, e);
   |                                       ^^^ cannot move out of borrowed content

I would like to achieve this without requiring the Copy trait as I have some boxed types inside and ideally would like to remove the need for the dummy variable (A::zero()).


Solution

  • If I understand your code, A will be some number because it implements Zero. The numeric values implement Copy in Rust, so you can use this to your advantage:

    pub fn push(&mut self, e: A)
    where
        A: Zero<A> + Copy,
    {
        match self {
            D::E1(e1) => *self = D::E2(e, *e1),
            _ => unimplemented!(),
        };
    }
    

    If you do not want to bound against Copy, you can use std::mem::replace as suggested:

    pub fn push(&mut self, e: A)
    where
        A: Zero<A>,
    {
        use std::mem::replace;
        match self {
            D::E1(e1) => *self = D::E2(e, replace(e1, A::zero())),
            _ => unimplemented!(),
        };
    }
    

    That is the idiomatic way to do so.

    You need the dummy element because you cannot do this:

    D::E1(e1) => *self = D::E2(e, *e1),
    

    The reason is that the compiler evaluates the parameters first, and *e1 tries to take the ownership of borrowed data, but that is forbidden.