Mutate enum in place


#1

Hi,

I need to mutate an enum along those lines:

enum Bla {
    A(X, Y),
    B(X),
}

impl Bla {
    fn to_b(&mut self) {
         // if A, put X into B, else do nothing.
    }
}

What is the best way to do this?


#2

This is tricky because the value referenced by self needs to be valid at all times. For example, if your function to_b panics at some point in the middle, and the stack is going to be unwound, the value must not be in some invalid intermediate state.

You have several different options.

  1. Use self instead of &mut self:
enum Bla<X, Y> {
    A(X, Y),
    B(X),
}

impl<X, Y> Bla<X, Y> {
    fn to_b(self) -> Self {
        match self {
            Bla::A(a, _) => Bla::B(a),
            v => v
        }
    }
}
  1. Add a variant where you don’t need to store any data:
enum Bla<X, Y> {
    A(X, Y),
    B(X),
    None
}

impl<X, Y> Bla<X, Y> {
    fn to_b(&mut self) {
        *self = match std::mem::replace(self, Bla::None) {
            Bla::A(a, _) => Bla::B(a),
            v => v
        }
    }
}
  1. Insert some default value while you’re doing the swap:
enum Bla<X, Y> {
    A(X, Y),
    B(X),
}

impl<X: Default, Y> Bla<X, Y> {
    fn to_b(&mut self) {
        *self = match std::mem::replace(self, Bla::B(Default::default())) {
            Bla::A(a, _) => Bla::B(a),
            v => v
        }
    }
}
  1. Use unsafe code. I think this code is correct, but I’m not sure!
use std::ptr;

enum Bla<X, Y> {
    A(X, Y),
    B(X),
}

impl<X, Y> Bla<X, Y> {
    fn to_b(&mut self) {
        unsafe {
            ptr::write(self, match ptr::read(self) {
                Bla::A(a, _) => Bla::B(a),
                v => v
            })
        }
    }
}

#3

I already knew about the first variant. Your other three variants are very instructive! I couldn’t get it to work and now I see why. (At first I assumed that this would work with NLL, but it didn’t.)


#4

The take_mut crate provides a safe way to write @jethrogb’s 4th implementation.

extern crate take_mut;

impl Bla {
    fn to_b(&mut self) {
        take_mut::take(self, |bla| match bla {
            Bla::A(a, _) => Bla::B(a),
            v => v,
        })
    }
}

#5

Good to know, thanks!