首页 > 解决方案 > 用 enum_dispatch 替换 trait 实现者的模式匹配

问题描述

根据这篇文章“不要将装箱的 trait 对象用于 struct internals ”,将 trait 对象存储在 struct 字段中是一个坏主意,因为我们会丢失有关对象的有价值的类型信息。所以他们建议为我们所有的 trait 实现者创建一个枚举包装器。

在下面的示例中,trait Person有实现者struct Mestruct Grandma,我们希望将这两种实现者都存储在 中struct PeopleZoo.people

但是因为我们想在PeopleZoo.people字段中保留关于我们的实现者的类型信息,所以我们创建了一个包装器 enum Person,而不是使用Box<dyn Person>

//our common trait
trait Person {
    fn say_hello(&self);
}
//trait implementor 1
struct Me {
    name: &'static str,
}

impl Person for Me {
    fn say_hello(&self) {
        println!("Hello, it's me.")
    }
}

struct Grandma {
    age: usize
}

impl Person for Grandma {
    fn say_hello(&self) {
        println!("G'day!")
    }
}

enum People {
    Grandma(Grandma),
    Me(Me)
}
//tedious pattern matching statement
impl Person for People {
    fn say_hello(&self) {
        match self {
            People::Grandma(grandma) => grandma.say_hello(),
            People::Me(me) => me.say_hello()
        }
    }
}

struct PeopleZoo<P: Person> {
    people: Vec<P>,
}

impl<P: Person> PeopleZoo<P> {
    fn add_person(&mut self, person: P) {
        self.people.push(person);
    }

    fn last_person(&self) -> Option<&P> {
        self.people.last()
    }
}

fn main() {
    let mut zoo: PeopleZoo<People> = PeopleZoo { people: vec![] };
    zoo.add_person(People::Me(Me { name: "Bennett" }));
    
    if let Some(People::Me(me)) = zoo.last_person() {
        println!("My name is {}.", me.name)
    }
}

问题是为我们所有的特征函数编写模式匹配语法Person会变得相当乏味。据我了解,enum_dispatch将为我们完成一些模式匹配工作,并自动实现Person for People。但我不确定如何让它工作。

use enum_dispatch::enum_dispatch;

#[enum_dispatch(Person)]
trait Person {
    fn say_hello(&self);
}
//implementor struct 1
struct Me {
    name: &'static str,
}

impl Person for Me {
    fn say_hello(&self) {
        println!("Hello, it's me.")
    }
}
//implementor struct 2
struct Grandma {
    age: usize
}

impl Person for Grandma {
    fn say_hello(&self) {
        println!("G'day!")
    }
}

#[enum_dispatch]
enum People {
    Grandma(Grandma),
    Me(Me)
}
//I thought enum_dispatch generated this impl for us automatically, so comment out
//impl Person for People {
//    fn say_hello(&self) {
//        match self {
//           People::Grandma(grandma) => grandma.say_hello(),
//            People::Me(me) => me.say_hello()
//        }
//    }
//}

struct PeopleZoo<P: Person> {
    people: Vec<P>,
}

impl<P: Person> PeopleZoo<P> {
    fn add_person(&mut self, person: P) {
        self.people.push(person);
    }

    fn last_person(&self) -> Option<&P> {
        self.people.last()
    }
}

fn main() {
    let mut zoo: PeopleZoo<People> = PeopleZoo { people: vec![] };
    zoo.add_person(People::Me(Me { name: "Bennett" }));
    
    if let Some(People::Me(me)) = zoo.last_person() {
        println!("My name is {}.", me.name)
    }
}

给出错误:

error[E0599]: the method `last_person` exists for struct `PeopleZoo<People>`, but its trait bounds were not satisfied
  --> src\main.rs:63:39
   |
30 | enum People {
   | ----------- doesn't satisfy `People: Person`
...
44 | struct PeopleZoo<P: Person> {
   | --------------------------- method `last_person` not found for this
...
63 |     if let Some(People::Me(me)) = zoo.last_person() {
   |                                       ^^^^^^^^^^^ method cannot be called on `PeopleZoo<People>` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `People: Person`

我是否正确理解 enum_dispatch 的功能?如果是这样,我怎样才能让这个例子工作?

标签: rust

解决方案


改变

#[enum_dispatch(Person)]
trait Person {
    fn say_hello(&self);
}

#[enum_dispatch(People)]
trait Person {
    fn say_hello(&self);
}

并且代码按预期工作。我需要将我们的包装枚举传递enum_dispatch给我们的特征的属性Person


推荐阅读