【问题标题】:Trait with default implementation and required struct member具有默认实现和所需结构成员的特征
【发布时间】:2020-12-20 14:03:02
【问题描述】:

我有一个 rust 特征,它应该为向量添加一个值。为了使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());
}

有没有办法让 trait 也提供结构成员?

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

【问题讨论】:

    标签: rust traits


    【解决方案1】:

    Traits 不能提供或要求结构字段。虽然有一个RFC (#1546) 允许在特征中使用字段。但是,没有任何不稳定的功能允许这样做(还没有?)。


    不过,您仍然可以简化您正在尝试做的事情。我冒昧地重命名并更改了您的特征,以便能够提供更详尽的示例。

    假设我们有一个Jobs 特征。其中定义了各种方法,都需要jobs: 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 特征

    另一个解决方案可能是引入第二个 HasJobs 特征。然后,如果类型实现了HasJobs,则可以将blanket implementation 用于Jobs

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

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

    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

    最后,如果Customer 始终是Person,那么您可以将Person 更改为struct 并使用组合,即将person: Person 添加到Customer(和其他类型)。

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

    那么现在您可以使用DerefDerefMutCustomer 取消引用到Person。因此Person 的所有方法都可以直接通过Customer 获得。

    然而,这个解决方案只有有效如果你只有一个“特征”,即你只能DerefCustomerPerson。你也不能Deref CustomerSomethingElse

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

    【讨论】:

    • 作为评论,有一些 RFC 允许在特征上使用字段
    • 我已经在找它了。但无论如何,感谢您提及! :)
    • 很好的答案,超出了我的预期!
    猜你喜欢
    • 2016-09-19
    • 1970-01-01
    • 2019-12-08
    • 2021-05-18
    • 2020-12-19
    • 2016-07-19
    • 1970-01-01
    • 1970-01-01
    • 2012-11-22
    相关资源
    最近更新 更多