【问题标题】:How can I select an enum variant based on a generic type that matches a type inside the variant?如何根据与变体中的类型匹配的泛型类型选择枚举变体?
【发布时间】:2021-06-30 22:35:53
【问题描述】:

我有多种类型和一个枚举:

struct Foo;
struct Bar;

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

有没有办法为此创建一个通用的.push 方法,比如

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

这不会编译,因为 .push 只看到泛型类型 T,而不是它所期望的类型。

有没有办法做到这一点?

【问题讨论】:

  • 如果Tself 的错误类型怎么办?
  • 该方法会返回错误,为简洁起见,我将其省略

标签: generics rust


【解决方案1】:

反转逻辑 - 引入一个特征以根据类型选择适当的枚举变体,然后从您的方法中调用该特征:

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

请注意,对于无法存储在枚举中的类型,这会导致编译时错误

对于大型枚举,可能值得创建一个宏来减少多个 Selector 实现的重复。

另见:

【讨论】:

  • 谢谢,这太完美了!
【解决方案2】:

这个最简单的方法是创建一个内部enum 并接受:

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(...),
    })
}

然后,如果我们使用 crate derive_more,我们可以轻松地执行以下操作:

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(...),
    })
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-02-14
    • 1970-01-01
    • 1970-01-01
    • 2016-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多