How to get a clone of value in an enum?

#[derive(Debug, Clone)]
enum TestEnum {
    A{ x: i32, y: String},
    B(String),
    C(i32),
}

#[derive(Debug, Clone)]
struct TestStruct {
    x: i32,
    y: String,
}

It's easy to get a clone of a value in a struct, since we can use dot to get its fields directly.

let a = TestStruct { x: 1, y: "Hello".to_string() };
let y = a.y.clone();

For enums, it can be done using a pattern match

let test = TestEnum::B(1, String::from("world!"));
let x = if let TestEnum::B(_, ref y) = test {
        y.clone()
    } else {
        String::from("Hello, world!")
    };

because variants in enums may be very different, I understand it's not easy to get a value in enums as we do in structs. But, I wonder is there any easier way to do such thing, since sometimes I want test still valid.

In principle, pattern matches that become repetitive can be hidden behind function calls, that can act as combinators to write out your actions in a more compact manner. The enum Option presents this possibility quite well with the presence of its countless little helper methods for different ways of handling the different cases.

The API of Option also demonstrates well, that accounting for all kinds of possible use-cases can result in a lot of API. If the goal is to simplify your code, that’s suboptimal, too, as it would merely shift the burden from use-site to helper function definition site.

Another reasonable approach for your enum can be to re-use the existing combinators of Option.

You can define methods for accessing a specific field once and then use it in various ways. Let’s try a concrete approach for your example. The example is not quite coherent as of now, since the definition of TestEnum::B features only a single field. But let’s simplify to something like

#[derive(Debug, Clone)]
enum TestEnum {
    A { x: i32, y: String },
    C(i32),
}

fn main() {
    let test = TestEnum::A {
        x: 1,
        y: String::from("world!"),
    };
    let x = if let TestEnum::A { y, .. } = &test {
        y.clone()
    } else {
        String::from("Hello, world!")
    };
    println!("{x}");
}

Now, the code in main wants to access the y field of the A variant in TestEnum. If we give it a method

impl TestEnum {
    fn a_y(&self) -> Option<&String> {
        let TestEnum::A { y, .. } = self else { None? };
        Some(y)
    }
}

then the main function can be written more shortly using this method, and Option’s API:

fn main() {
    let test = TestEnum::A {
        x: 1,
        y: String::from("world!"),
    };
    let x = test
        .a_y()
        .cloned()
        .unwrap_or_else(|| "Hello, world!".into());
    println!("{x}");
}

Rust Playground

Of course, the part of somehow handling the else case doesn’t disappear[1] but the main part of accessing field y of variant a and cloning it if present becomes as compact as test.a_y().cloned(), making use of the Option::cloned method from the standard library.


  1. well if you leave it off, you’d simply end up with an Option<String>, with can be useful, too – or in the context of yet-another -> Option<Something>-returning method, you could use the ? operator ↩︎

4 Likes

Thanks!
Let me share my understanding, in fact, pattern matching is the only way to get values wrapped in enums, but we can define methods for enums to re-use those codes.
Because different variants in a given enum may have different value layouts, Option is a good choice here, None means we didn't get the desired variant, and Some(value) means we get the desired variants and value wrapped in. Now we can use method chaining to do it, which is much tidier than using pattern matching directly.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.