vluzko
vluzko

Reputation: 335

Is there a way to create a copy of an enum with some field values updated?

I have an enum with several record variants:

enum A {
    Var1 { a: i64, b: i64 },
    Var2 { c: i32, d: i32 },
}

I want to create a modified copy of such an enum (with different behavior for each variant). I know I can do this:

match a {
    A::Var1 { a, b } => A::Var1 { a: new_a, b },
    A::Var2 { c, d } => A::Var2 { c, d: new_d },
}

However each variant has quite a few fields, and I'd prefer not to explicitly pass them all. Is there any way to say "clone this enum, except use this value for field x instead of the cloned value"?

Upvotes: 0

Views: 1739

Answers (2)

L117
L117

Reputation: 586

Not exactly. There's "functional record update syntax", but it's only for structs:

struct Foo {
    bar: u8,
    baz: u8,
    quux: u8,
}

fn foo() {
    let foobar = Foo {
        bar: 1,
        baz: 2,
        quux: 3,
    };
    let bazquux = Foo { baz: 4, ..foobar };
}

Best you can do without creating structs for each variant is something like this:

let mut altered = x.clone();
match &mut altered {
    A::Var1 { a, .. } => *a = new_a,
    A::Var2 { d, .. } => *d = new_d,
};
altered

Upvotes: 4

Sébastien Renauld
Sébastien Renauld

Reputation: 19662

I'm afraid you're hitting one of the restrictions on Rust based on its design and your only real solution is mutation in-place and writing a mutator function or four.

The problem with enums is that you need to match to be able to do anything on them. Until that point, Rust knows or infers very little about what the struct actually is. An additional problem is the lack of any kind of reflection-like ability to allow for the ability to query a type and figure out if it has a field, and the inability to do anything but exhaustively match all contents.

Honestly, the cleanest way may actually depend on the purpose of your mutations. Are they a defined set of changes to an enum based on a business concern of some sort? If so, you may actually want to wrap your logic into a trait extension and use that to encapsulate the logic.

Consider, for instance, a very contrived example. We're building an application that has to deal with different items and apply taxes to them. Said taxes depend on the type of products, and for some reason, all our products are represented by variants of an enum, like so: #[derive(Debug)] enum Item { Food { price: u8, calories: u8 }, Technology { price: u8 }, }

trait TaxExt {
    fn apply_tax(&mut self);
}
impl TaxExt for Item {
    fn apply_tax(&mut self) {
        match self {
            &mut Item::Food {
                ref mut price,
                calories: _,
            } => {
                // Our food costs double, for tax reasons
                *price *= 2;
            }
            &mut Item::Technology { ref mut price } => {
                // Technology has a 1 unit tax
                *price += 1;
            }
        }
    }
}

fn main() {
    let mut obj = Item::Food {
        price: 3,
        calories: 200,
    };
    obj.apply_tax();
    println!("{:?}", obj);
}

playground

Assuming you can split your logic like so, it is probably the cleanest way to structure this.

Upvotes: 1

Related Questions