简体   繁体   中英

How can I select an enum variant based on a generic type that matches a type inside the variant?

I have multiple types and an enum:

struct Foo;
struct Bar;

enum MyEnum {
    Foos(Vec<Foo>),
    Bars(Vec<Bar>),
}

Is there a way to make a generic .push method for this, like

impl MyEnum {
    fn push<T>(&mut self, item: T) {
        if item.type_id() == TypeId::of::<Foo>() {
            if let MyEnum::Foos(ref mut vec) = self {
                vec.push(item); // doesn't compile because item is generic type `T` instead of `Foo`
            }
        } else if item.type_id == TypeId::of::<Bar>() {
            if let MyEnum::Bars(ref mut vec) = self {
                vec.push(item);
            }
        }
    }
}

This doesn't compile because .push only sees generic type T , not the type it was expecting.

Is there a way to accomplish this?

Invert the logic — introduce a trait to select the appropriate enum variant based on the type, then call that trait from your method:

trait Selector: Sized {
    fn select_mut<'a>(&self, e: &'a mut MyEnum) -> Option<&'a mut Vec<Self>>;
}

impl Selector for Foo {
    fn select_mut<'a>(&self, e: &'a mut MyEnum) -> Option<&'a mut Vec<Self>> {
        match e {
            MyEnum::Foos(vec) => Some(vec),
            _ => None,
        }
    }
}

impl Selector for Bar {
    fn select_mut<'a>(&self, e: &'a mut MyEnum) -> Option<&'a mut Vec<Self>> {
        match e {
            MyEnum::Bars(vec) => Some(vec),
            _ => None,
        }
    }
}

impl MyEnum {
    fn push<T: Selector>(&mut self, item: T) {
        if let Some(v) = item.select_mut(self) {
            v.push(item)
        }
    }
}

#[derive(Debug)]
struct Foo;
#[derive(Debug)]
struct Bar;

#[derive(Debug)]
enum MyEnum {
    Foos(Vec<Foo>),
    Bars(Vec<Bar>),
}

fn main() {
    let mut e = MyEnum::Foos(vec![]);
    e.push(Foo);
    e.push(Bar);
    // e.push(false); // the trait bound `bool: Selector` is not satisfied
    dbg!(e);
}

Note that this results in a compile-time error for types that cannot be stored in the enum.

For large enums, it might be worth creating a macro to reduce the duplication of the multiple Selector implementations.

See also:

This simplest way is to make an inner enum and accept that:

enum MyEnumInner {
    Foo(Foo),
    Bar(Bar),
}

fn push_enum(&mut self, item: MyEnumInner) -> Result<()> {
    Ok(match (self, item) {
        (MyEnum::Foos(foos), MyEnumInner::Foo(foo)) => foos.push(foo),
        (MyEnum::Bars(bars), MyEnumInner::Bar(bar)) => bars.push(bar),
        _ => return Err(...),
    })
}

Then if we use the crate derive_more we can just as easily do the following:

use derive_more::From;

#[derive(From)]
enum MyEnumInner {
    Foo(Foo),
    Bar(Bar),
}

fn push<T: Into<MyEnumInner>>(&mut self, item: T) -> Result<()> {
    Ok(match (self, item.into()) {
        (MyEnum::Foos(foos), MyEnumInner::Foo(foo)) => foos.push(foo),
        (MyEnum::Bars(bars), MyEnumInner::Bar(bar)) => bars.push(bar),
        _ => return Err(...),
    })
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM