Change enum variant while moving the field to the new variant

If you want to do this without moving out of the value in a zero-cost way, you have to resort to a bit of unsafe code (AFAIK):

use std::mem;

#[derive(Debug)]
enum X {
    X1(String),
    X2(String),
}

fn increment_x(x: &mut X) {
    let interim = unsafe { mem::uninitialized() };
    let prev = mem::replace(x, interim);
    let next = match prev {
        X::X1(s) => X::X2(s),
        X::X2(s) => X::X1(s),
    };
    let interim = mem::replace(x, next);
    mem::forget(interim); // Important! interim was never initialized
}

This doesn't work because we can't move s from &mut X.

Then don't do that... take the struct by value and return a new one:

enum X {
    X1(String),
    X2(String),
}

fn increment_x(x: X) -> X {
    match x {
        X::X1(s) => X::X2(s),
        X::X2(s) => X::X1(s),
    }
}

Ultimately, the compiler is protecting you because if you could move the string out of the enumeration, then it would be in some half-constructed state. Who would be responsible for freeing the string if the function were to panic at that exact moment? Should it free the string in the enum or the string in the local variable? It can't be both as a double-free is a memory-safety issue.

If you had to implement it on a mutable reference, you could store a dummy value in there temporarily:

use std::mem;

fn increment_x_inline(x: &mut X) {
    let old = mem::replace(x, X::X1(String::new()));
    *x = increment_x(old);
}

Creating an empty String isn't too bad (it's just a few pointers, no heap allocation), but it's not always possible. In that case, you can use Option:

fn increment_x_inline(x: &mut Option<X>) {
    let old = x.take();
    *x = old.map(increment_x);
}

See also:

  • How can I swap in a new value for a field in a mutable reference to a structure?
  • Temporarily move out of borrowed content (specifically this answer)
  • How do I move out of a struct field that is an Option?

In some particular cases, what you want is in fact std::rc

enum X {
    X1(Rc<String>),
    X2(Rc<String>),
}

fn increment_x(x: &mut X) -> X {
    match x {
        X::X1(s) => {x = X::X2(s.clone())},
        X::X2(s) => {x = X::X1(s.clone())},
    }
}

Tags:

Rust