14

Is it possible to switch variants in the value of a mutable reference (&mut E<T>) without additional constraints on T, and without resorting to unsafe code?

That is, given an enum:

enum E<T> {
    VariantA(T),
    VariantB(T)
}

What is the correct way of writing this:

let x: E<???> = E::VariantA(??);
change_to_variant_b(&mut x);
assert_eq!(x, E::VariantB(??));
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Doe
  • 585
  • 5
  • 19
  • 1
    Looks like something that would be addressed by the closed [`replace_with` RFC (#1736)](https://github.com/rust-lang/rfcs/pull/1736). – kennytm Feb 24 '17 at 07:59
  • 4
    As an alternative, would changing it to a `struct` with a `T` and a plain `enum` work in your case? – Chris Emerson Feb 24 '17 at 09:23
  • @ChrisEmerson It very much would. I just thought that there must be a way for variants to assume the contents of their siblings since it intuitively doesn't run counter to Rusts ownership model. – Doe Feb 24 '17 at 13:29
  • I'm unsure whether this is close enough to the original problem to warrant an answer, but if you are willing to have a variant that does not contain a `T`, and to tolerate an "impossible" path though the code, I've written a safe, stable, version of `change_to_variant_b` [here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f731602e09c7f8be91fd4a21d8c0e7ca) which uses `std::mem::replace`. – Ryan1729 Feb 02 '21 at 07:43

3 Answers3

8

I am going to go on a limb here and say No.


It is possible with just a minor change to the signature though:

fn change_to_variant_b<T>(e: E<T>) -> E<T> {
    match e {
        E::VariantA(t) => E::VariantB(t),
        E::VariantB(t) => E::VariantB(t),
    }
}

It is possible using unsafe:

fn change_to_variant_b<T>(e: &mut E<T>) {
    use std::ptr;

    unsafe {
        match ptr::read(e as *const _) {
            E::VariantA(t) => ptr::write(e as *mut _, E::VariantB(t)),
            E::VariantB(t) => ptr::write(e as *mut _, E::VariantB(t)),
        }
    }
}

It is possible with additional bounds (Default, or Clone):

fn change_to_variant_b<T: Default>(e: &mut E<T>) {
    match std::mem::replace(e, E::VariantA(T::default())) {
        E::VariantA(t) => e = E::VariantB(t),
        E::VariantB(t) => e = E::VariantB(t),
    }
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • Props for the clear answer and for listing alternatives in detail. I need `&mut E` because I'm mutably iterating over a slice, so I can't(?) move elements out of it temporarily for the first solution to work. – Doe Feb 24 '17 at 13:35
  • Also, as a paragmatic solution, see https://stackoverflow.com/questions/42431973/how-to-change-variants-of-mutable-reference#comment72014156_42431973 – Doe Feb 24 '17 at 13:35
  • @Doe: Oh, if Chris solution works for you then definitely roll with it. It's always a bit difficult to come up with alternatives working on reduced examples since it's unclear what the boundaries of what can and cannot be changed are. – Matthieu M. Feb 24 '17 at 13:45
  • @perhaps "solution" wasn't the best word to describe Chris's comment (as it side-steps the issue entirely). However, for most visitors who are looking for a solution, Chris's comment will likely suffice. **No** is probably the only answer that meets the criteria in the OP. – Doe Feb 24 '17 at 14:02
  • @Doe: Yes, Chris, like mine, are more work-arounds. If you are (1) stuck with the `enum` and (2) stuck with the signature of the function and (3) stuck with no unsafe/no bounds... then you cannot. – Matthieu M. Feb 24 '17 at 15:32
  • I'm coming to this question from Google, how come nobody suggested `std::mem::replace` on `self`? – kalkronline Nov 03 '22 at 17:41
2

As an alternative for this particular case, you could consider changing to a struct with a T and a plain enum:

struct Outer<T> {
    val: T,
    kind: Inner,
}

impl<T> Outer<T> {
    fn promote_to_b(&mut self) {
        self.kind.promote_to_b()
    }
}

enum Inner {
    VariantA,
    VariantB,
}

impl Inner {
    fn promote_to_b(&mut self) {
        if let Inner::VariantA = *self {
            *self = Inner::VariantB;
        }
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
1

is it possible to switch variants in the value of a mutable reference

As Matthieu M. said, the general answer is "no". The reason is that doing so would leave the enum in an indeterminate state, which would allow accessing undefined memory, which would allow breaking Rust's safety guarantees. As an example, let's pretend that this code compiled without error:

impl<T> E<T> {
    fn promote_to_b(&mut self)  {
        if let E::VariantA(val) = *self {
            // Things happen
            *self = E::VariantB(val);
        }
    }
}

The problem is once you've moved the value from self into val, what should happen to the memory representing T inside self?

If we copied the bits and then a panic occurred in "Things happen", the destructor for both val and the T inside self would run, but since these point at the same data, this would lead to a double free.

If we didn't copy the bits, then you couldn't safely access the val inside "Things happen", which would be of marginal usefulness.


The by-value solution works because the compiler can track who is supposed to call the destructor: the function itself. Once you are inside the function, the compiler knows which specific lines might need to free the value and properly calls them in case of panic.

The Clone or Default solution works because you never move the value out of the original enum. Instead, you can replace the original enum with a dummy value and take ownership of the original (using Default) or duplicate the entire original value (using Clone).


The replace_with RFC (#1736) proposed to add a method that would allow this to work while ensuring that proper memory semantics were upheld, but that RFC was not accepted.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366