Converting an enum where all variants implement the same trait to a box in Rust?
Asked Answered
V

3

9

I have a trait Foo, with some implementations, together with an enum Foos with one variant per implemention. I want to be able to convert my enum into Box<dyn Foo>.

This is my current solution:

trait Foo {}

struct FooA {}
impl Foo for FooA {}

struct FooB {}
impl Foo for FooB {} 

struct FooC {}
impl Foo for FooC {}

enum Foos {
    A(FooA),
    B(FooB),
    C(FooC),
}

impl Foos {
    fn into_box(self) -> Box<dyn Foo> {
        match self {
            Foos::A(foo) => Box::new(foo),
            Foos::B(foo) => Box::new(foo),
            Foos::C(foo) => Box::new(foo),
        }
    }
}

It works, but there's a lot of boiler plate in into_enum. As the number of variants grow, so will the function. Is there a simpler way to do this? It feels like it should be a one liner!

Vishinsky answered 5/1, 2019 at 21:58 Comment(4)
"It feels like it should be a one liner!" primary opinion ? If you want you can create RFC for Rust.Miyokomizar
Also, why not impl Foo for Foos ?Miyokomizar
@Miyokomizar Foo has many methods and I don't want to have to do a match over the enum variants for every one of them.Vishinsky
yeah I think there is a keyword in preparation for this, still a macro could do the job very well.Miyokomizar
S
6

With the enum_dispatch crate, you can write

#[macro_use]
extern crate enum_dispatch;

#[enum_dispatch]
trait Foo {}

struct FooA {}
impl Foo for FooA {}

struct FooB {}
impl Foo for FooB {}

struct FooC {}
impl Foo for FooC {}

#[enum_dispatch(Foo)]
enum Foos {
    A(FooA),
    B(FooB),
    C(FooC),
}

to get a generated impl Foo for Foos. You can then convert Foos to Box<dyn Foo> with simply Box::new.

There is a potential downside to this approach: Box::new(Foos::A(FooA)) contains a Foos, not an FooA, so it will incur the overhead of both the dynamic dispatch from dyn Foo to Foos and the enum dispatch from Foos to FooA.

On the other hand, now that you have impl Foo for Foos: everywhere you would have used Box<dyn Foo>, you’ll instead be able to directly use Foos, which should be more efficient in every way.

Shamblin answered 5/1, 2019 at 23:36 Comment(0)
T
5

I recently wanted something similar. I cannot offer you a one-liner, but a macro that automatically generates the respective match arms along with then enum variants:

macro_rules! impl_foos{($($enumvariant: ident($foo: ty),)*) => {
    enum Foos {
        $($enumvariant($foo),)*
    }
    impl Foos {
        fn into_enum(self) -> Box<dyn Foo> {
            match self {
                $(Foos::$enumvariant(foo) => Box::new(foo),)*
            }
        }
    }
}}

impl_foos!(
    A(FooA),
    B(FooB),
    C(FooC),
);

This way, there is only one place to maintain all the possibilities, everything else is generated. Maybe even the crate enum_dispatch helps.

Off-topic: Should it really be into_enum(self)->Box<dyn Foo>? Shouldn't it be something like as_foo(&self)->&dyn Foo?

Tomlinson answered 5/1, 2019 at 22:51 Comment(1)
Sorry, into_enum was meant to be into_box, my bad. I have updated the question. Intersting that you can return &dyn, I did not know that. Thanks!Vishinsky
B
0

It is very easy to convert from enum to trait, and then Box that. Yes a bit boilerplate, but not as much as posted, reusable, and your IDE should create all the arms.

trait MyTrait {
}

enum MyEnum {
  Var1(SomeTraitStruct),
  Var2(AnotherTraitStruct),
}

impl MyEnum {
    fn to_my_trait(&self) -> &dyn MyTrait {
        match self {
            MyEnum::Var1(it) => { it }
            MyEnum::Var2(it) => { it }
        }
    }
}

fn test() {
  let value = MyEnum::Var1(SomeTraitStruct{})
  let my_trait = value.to_my_trait();
  let my_box:Box<&dyn MyTrait> = Box::new(my_trait); 
}
Boney answered 30/6, 2023 at 22:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.