Трейт с реализацией по умолчанию и обязательным элементом структуры

У меня есть черта ржавчины, которая должна добавлять значение к вектору. Чтобы функция 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());
}

Есть ли способ иметь трейт, который также предоставляет члены структуры?

Возможно, нет, но каким будет «ржавый» способ решить вышеуказанную проблему?

Почему Python в конце концов умрет.
Почему Python в конце концов умрет.
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
10
0
7 618
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Черты не могут предоставлять или требовать поля структуры. Хотя есть 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. Затем вы можете использовать общую реализацию для 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);

Использование Deref и DerefMut

Наконец, если 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
    }
}

В качестве комментария, есть несколько RFC, разрешающих поля для признаков.

Chayim Friedman 20.12.2020 15:29

Я уже искал это. Но все равно спасибо за упоминание! :)

vallentin 20.12.2020 15:30

Отличный ответ, больше, чем я надеялся!

LongHike 20.12.2020 16:29

Спасибо за этот ответ из-за его ссылок! Я программирую на Rust уже как минимум год, в том числе и в продакшене, но я только что узнал, что такое реализации Blanket. Продолжайте видеть это в документации, это помогает мне лучше понять, как работает тип, но не знаю, как реализовать это самостоятельно. И ЭТО УДИВИТЕЛЬНО ЗНАТЬ ОБ ЭТОМ. Будет реорганизовано много моих кодов.

MikeTheSapien 02.06.2022 04:34

Другие вопросы по теме