具有默认实现和所需结构成员的特征

Trait with default implementation and required struct member

提问人:LongHike 提问时间:12/20/2020 最后编辑:vallentinLongHike 更新时间:12/20/2020 访问量:10404

问:

我有一个生锈特征,它应该为向量添加一个值。为了使函数正常工作,必须确保在为具体结构实现特征时存在向量。add_job

当然,以下代码会失败,因为作业从未实现。它只是为了证明我的意图:

trait Person {
    // default implementation of add job
    fn add_job(&self, job: String) {
        self.jobs.push(job)
    }
}

struct Customer {
    // add_job is used as default implementation 
    // provided by trait
}

impl Person for Customer {
    // some stuff
}

fn main() {
    let mut george = Customer {};
    george.add_job("programmer".to_string());
}

有没有办法拥有也提供结构成员的特征?

可能不是,但是解决上述问题的“生锈”方法是什么?

蚀性状

评论


答:

19赞 vallentin 12/20/2020 #1

Traits 不能提供或需要结构字段。尽管有一个关于允许特征中的字段的 RFC (#1546)。但是,没有任何不稳定的功能允许这样做(还没有?


不过,您仍然可以简化您正在尝试执行的操作。我冒昧地重命名和更改了你的特征,以便能够提供更全面的例子。

让我们考虑我们有一个特质。它定义了各种方法,这些方法都需要该字段。Jobsjobs: Vec<String>

trait Jobs {
    fn add_job(&mut self, job: String);
    fn clear_jobs(&mut self);
    fn count_jobs(&self) -> usize;
}

使用宏

一种解决方案是使用 实现所有这些方法的 ,它实现了所有这些方法。macro

macro_rules! impl_jobs_with_field {
    ($($t:ty),+ $(,)?) => ($(
        impl Jobs for $t {
            fn add_job(&mut self, job: String) {
                self.jobs.push(job);
            }

            fn clear_jobs(&mut self) {
                self.jobs.clear();
            }

            fn count_jobs(&self) -> usize {
                self.jobs.len()
            }
        }
    )+)
}

然后,您可以使用宏轻松重用代码。

struct Person {
    jobs: Vec<String>,
}

struct Customer {
    jobs: Vec<String>,
}

impl_jobs_with_field!(Person);
impl_jobs_with_field!(Customer);
// or
impl_jobs_with_field!(Person, Customer);

使用第二个特征HasJobs

另一种解决方案可能是引入第二个特征。然后,如果某个类型实现,则可以使用一揽子实现HasJobsJobsHasJobs

trait HasJobs {
    fn jobs(&self) -> &[String];
    fn jobs_mut(&mut self) -> &mut Vec<String>;
}

impl<T: HasJobs> Jobs for T {
    fn add_job(&mut self, job: String) {
        self.jobs_mut().push(job);
    }

    fn clear_jobs(&mut self) {
        self.jobs_mut().clear();
    }

    fn count_jobs(&self) -> usize {
        self.jobs().len()
    }
}

现在仍然需要为所有类型实现。但是如果有大量的方法。那么实施就容易多了。我们也可以使用宏来做到这一点:HasJobsJobsHasJobs

macro_rules! impl_has_jobs {
    ($($t:ty),+ $(,)?) => ($(
        impl HasJobs for $t {
            fn jobs(&self) -> &[String] {
                &self.jobs
            }

            fn jobs_mut(&mut self) -> &mut Vec<String> {
                &mut self.jobs
            }
        }
    )+)
}

再一次,你只需要做:

struct Person {
    jobs: Vec<String>,
}

struct Customer {
    jobs: Vec<String>,
}

impl_has_jobs!(Person);
impl_has_jobs!(Customer);
// or
impl_has_jobs!(Person, Customer);

使用 和DerefDerefMut

最后,如果始终是 ,那么您可以更改为 a 并使用组合,即 add to(和其他类型)。CustomerPersonPersonstructperson: PersonCustomer

所以首先,:impl Jobs for Person

struct Person {
    jobs: Vec<String>,
}

impl Jobs for Person {
    fn add_job(&mut self, job: String) {
        self.jobs.push(job);
    }

    fn clear_jobs(&mut self) {
        self.jobs.clear();
    }

    fn count_jobs(&self) -> usize {
        self.jobs.len()
    }
}

那么现在你可以使用 DerefDerefMut 来取消对 .因此,所有的方法都可以通过 直接获得。CustomerPersonPersonCustomer

但是,此解决方案仅在您只有一个“特征”时才有效,即您只能 .你也不能.DerefCustomerPersonDerefCustomerSomethingElse

use std::ops::{Deref, DerefMut};

struct Customer {
    person: Person,
}

impl Deref for Customer {
    type Target = Person;

    fn deref(&self) -> &Self::Target {
        &self.person
    }
}

impl DerefMut for Customer {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.person
    }
}

评论

1赞 Chayim Friedman 12/20/2020
作为评论,有一些 RFC 允许对特征进行字段
0赞 vallentin 12/20/2020
我已经在寻找它了。但无论如何,感谢您的提及!:)
0赞 LongHike 12/20/2020
伟大的答案,比我所希望的要多!
0赞 MikeTheSapien 6/2/2022
感谢您的回答,因为它的参考资料!用 Rust 编码至少一年了,也在生产环境中编码,但我刚刚发现了什么是一揽子实现。继续在文档中看到它,它让我了解类型如何更好地发挥作用,但不知道如何自己实现它。知道这一点真是太神奇了。将重构我的很多代码。