Unwrapping struct members part of enums

struct A{}
struct B{}

trait Foo{
   fn bar(&self);
}

impl Foo for A{
	fn bar(&self){}
}

impl Foo for B {
	fn bar(&self){}
}

enum En{A(A),B(B)}

fn factory(arg:En){
	match arg {
		En::A(val) => val.bar(),
		En::B(val) => val.bar(),
		_ => {},
	}
}

fn main()
{
   let arg = En::A(A{});
   factory(arg);
}

In factory() I'm unwrapping struct from enum En and calling bar() .
my first question:the way I'm unwrapping enum members are correct ? or is there any better way to do this?
Second question: since bar() is common for both the switch arms, is there any better way to do this ? (why because Suppose if I have 10 struct members in enum En then I will end up writing 10 match arms doing the same thing)

You can impl Foo for En. This still requires the potentially large match statement, but you’ll only need to write it once. Everywhere else, you can just call En::bar().

1 Like

Seems alright. Only comment I have is that the trailing _ => default case is unnecessary (as it’s unreachable, which is by the way also something the compiler will be telling you here).

Furthermore, it is possible to simplify call-sites of the bar method, in case there’s multiple of those, by implementing the Foo trait for the enum, too, e.g.

impl Foo for En {
    fn bar(&self) {
        match self {
            En::A(val) => val.bar(),
            En::B(val) => val.bar(),
        }
    }
}

fn factory(arg: En) {
    arg.bar();
}

The answer to this question is yes and no. No, in that there’s no good support for sparing you to write such a long match for the enum from the language itself. Yes, in that Rust has a quite powerful macro system, so there are crates out there that can write implementations of a trait like the impl Foo for En above for you automatically. E.g. one such crate is enum_dispatch, which would allow you to write the following:

use enum_dispatch::enum_dispatch;

struct A {}
struct B {}

#[enum_dispatch(En)]
trait Foo {
    fn bar(&self);
}

impl Foo for A {
    fn bar(&self) {}
}

impl Foo for B {
    fn bar(&self) {}
}

// note that the fact that these variants say `A` instead of `A(A)` is just
// a (perhaps slightly weird) convention/abbreviation by the `enum_dispatch` crate
#[enum_dispatch]
enum En {
    A,
    B,
}

fn factory(arg: En) {
    arg.bar();
}

fn main() {
    let arg = En::A(A {});
    factory(arg);
}

or

use enum_dispatch::enum_dispatch;

struct A {}
struct B {}

#[enum_dispatch]
trait Foo {
    fn bar(&self);
}

impl Foo for A {
    fn bar(&self) {}
}

impl Foo for B {
    fn bar(&self) {}
}

#[enum_dispatch(Foo)]
enum En {
    A,
    B,
}

fn factory(arg: En) {
    arg.bar();
}

fn main() {
    let arg = En::A(A {});
    factory(arg);
}

Another crate is ambassador which allows you to write

use ambassador::{delegatable_trait, Delegate};

struct A {}
struct B {}

#[delegatable_trait]
trait Foo {
    fn bar(&self);
}

impl Foo for A {
    fn bar(&self) {}
}

impl Foo for B {
    fn bar(&self) {}
}

#[derive(Delegate)]
#[delegate(Foo)]
enum En {
    A(A),
    B(B),
}

fn factory(arg: En) {
    arg.bar();
}

fn main() {
    let arg = En::A(A {});
    factory(arg);
}
1 Like

These crates have shared global state in their proc macro to copy enum layout information from the derive location to call site. I'm afraid this is a hacky and fragile approach, and I'd not recommend using such crates.

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.