rustenums

Is there a way to overwrite an enum variable and get a reference to the associated data together?


Here is a simple program that overwrites an enum variable with another variant with associated data, and then needs a reference to that associated data afterward. (A more realistic example would have associated data that can't easily be copied.)

fn choose_u64() -> u64 { 1234 }

#[derive(Debug)]
enum E {
    V1(),
    V2(u64)
}

fn main() {
    // Set up some enum-typed variable.
    let mut e = E::V1();

    // Switch it to contain variant 2, with an associated value, and get a
    // reference to that value.
    e = E::V2(choose_u64());
    let E::V2(n) = &e else {
        panic!("Shouldn't get here");
    };
    
    println!("I emplaced: {n}");
}

Is there any better idiom for this that doesn't require boilerplate to convince the compiler that you checked for something that can never happen (the else panic branch)? I know you can do the same with a match expression, but that is even more boilerplate. I'm hoping for some construct like std::variant::emplace from C++, which provides the caller with a reference to the value they just emplaced.

As a secondary question that informs how important the first is: is the compiler likely in practice to actually generate any code for the impossible branch in a release build, or is it purely a syntactic annoyance?


Solution

  • I'm hoping for some construct like std::variant::emplace from C++, which provides the caller with a reference to the value they just emplaced.

    AFAIK there's no such thing:

    1. Rust has pretty poor support for in-place initialisation in general, still.
    2. It makes less sense from an enum being a sum type rather than a type union: the value is V2(u64) as a unit, therefore even with a helper there is no real way to handle this case any other way than as a two-step, at most you can use unsafe APIs for the second step (see below).
    3. It's a bit awkward but enums being a core language component means there's no userland building blocks for them, hence it's harder to build utilities around them: you need intrinsics to manipulate them, so you're at the mercy of whatever intrinsics are available.

    You could set a primitive representation for your enum to make sure they have a stable memory representation and retrieve the value pointer directly, but that's a bit...

    As a secondary question that informs how important the first is: is the compiler likely in practice to actually generate any code for the impossible branch in a release build, or is it purely a syntactic annoyance?

    If you plug your snippet into godbolt you'll see that with -O it doesn't even reference the panic entry point, so I'd say it's mostly the latter.

    Also FWIW in this case you should probably use unreachable!. It's effectively an alias for panic! (and will indeed panic) but it provides better classification for automated tooling, and hinting to readers.