У меня есть черта ржавчины, которая должна добавлять значение к вектору. Чтобы функция 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());
}
Есть ли способ иметь трейт, который также предоставляет члены структуры?
Возможно, нет, но каким будет «ржавый» способ решить вышеуказанную проблему?
Черты не могут предоставлять или требовать поля структуры. Хотя есть 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. Затем вы можете использовать общую реализацию для Jobs, если тип реализует HasJobs.
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);
Наконец, если 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()
}
}
Теперь вы можете использовать Deref и DerefMut для разыменования Customer в Person. Таким образом, все методы Person доступны напрямую через Customer.
Однако это решение работает только в том случае, если у вас есть только одна «черта», то есть вы можете только Deref Customer до Person. Вы бы не смогли также Deref Customer до SomethingElse.
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
}
}
Я уже искал это. Но все равно спасибо за упоминание! :)
Отличный ответ, больше, чем я надеялся!
Спасибо за этот ответ из-за его ссылок! Я программирую на Rust уже как минимум год, в том числе и в продакшене, но я только что узнал, что такое реализации Blanket. Продолжайте видеть это в документации, это помогает мне лучше понять, как работает тип, но не знаю, как реализовать это самостоятельно. И ЭТО УДИВИТЕЛЬНО ЗНАТЬ ОБ ЭТОМ. Будет реорганизовано много моих кодов.
В качестве комментария, есть несколько RFC, разрешающих поля для признаков.