Anders
Anders

Reputation: 8577

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

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!

Upvotes: 9

Views: 4662

Answers (3)

Steven Spungin
Steven Spungin

Reputation: 29091

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); 
}

Upvotes: 0

Anders Kaseorg
Anders Kaseorg

Reputation: 3875

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.

Upvotes: 6

phimuemue
phimuemue

Reputation: 35983

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?

Upvotes: 5

Related Questions