Как я могу избежать эффекта пульсации при изменении конкретной структуры на generic?

#rust

#дженерики #Ржавчина #Трейты

Вопрос:

У меня есть структура конфигурации, которая выглядит следующим образом:

 struct Conf {
    list: Vec<String>,
}
  

Реализация заполняла list элемент внутренне, но теперь я решил, что хочу делегировать эту задачу другому объекту. Итак, у меня есть:

 trait ListBuilder {
    fn build(amp;self, list: amp;mut Vec<String>);
}

struct Conf<T: Sized   ListBuilder> {
    list: Vec<String>,
    builder: T,
}

impl<T> Conf<T>
where
    T: Sized   ListBuilder,
{
    fn init(amp;mut self) {
        self.builder.build(amp;mut self.list);
    }
}

impl<T> Conf<T>
where
    T: Sized   ListBuilder,
{
    pub fn new(lb: T) -> Self {
        let mut c = Conf {
            list: vec![],
            builder: lb,
        };
        c.init();
        c
    }
}
  

Кажется, это работает нормально, но теперь везде, которые я использую Conf , я должен это изменить:

 fn do_something(c: amp;Conf) {
    // ...
}
  

становится

 fn do_something<T>(c: amp;Conf<T>)
where
    T: ListBuilder,
{
    // ...
}
  

Поскольку у меня много таких функций, это преобразование является болезненным, тем более что большинство вариантов использования Conf класса не заботятся о ListBuilder — это деталь реализации. Я обеспокоен тем, что если я добавлю другой универсальный тип в Conf , теперь мне придется вернуться и добавить еще один универсальный параметр везде. Есть ли какой-либо способ избежать этого?

Я знаю, что вместо этого я мог бы использовать замыкание для list builder, но у меня есть дополнительное ограничение, которым должна быть моя Conf структура Clone , а фактическая реализация builder более сложная и имеет несколько функций и некоторое состояние в builder, что делает подход закрытия громоздким.

Комментарии:

1. Привязка не требуется Sized ; это значение по умолчанию.

Ответ №1:

Хотя может показаться, что универсальные типы «заражают» остальной код, именно поэтому они полезны! Знания компилятора о том, насколько велик и конкретно какой тип используется, позволяют ему принимать лучшие решения по оптимизации.

При этом это может раздражать! Если у вас есть небольшое количество типов, которые реализуют вашу черту, вы также можете создать перечисление этих типов и делегировать дочерним реализациям:

 enum MyBuilders {
    User(FromUser),
    File(FromFile),
}

impl ListBuilder for MyBuilders {
    fn build(amp;self, list: amp;mut Vec<String>) {
        use MyBuilders::*;
        match self {
            User(u) => u.build(list),
            File(f) => f.build(list),
        }
    }
}

// Support code

trait ListBuilder {
    fn build(amp;self, list: amp;mut Vec<String>);
}

struct FromUser;
impl ListBuilder for FromUser {
    fn build(amp;self, list: amp;mut Vec<String>) {}
}

struct FromFile;
impl ListBuilder for FromFile {
    fn build(amp;self, list: amp;mut Vec<String>) {}
}
  

Теперь конкретным типом будет Conf<MyBuilders> , который вы можете скрыть с помощью псевдонима типа.

Я использовал это с хорошим эффектом, когда хотел иметь возможность внедрять тестовые реализации в код во время тестирования, но имел фиксированный набор реализаций, которые использовались в производственном коде.

Ящик enum_dispatch помогает создать этот шаблон.

Ответ №2:

Вы можете использовать объект trait Box<dyn ListBuilder> , чтобы скрыть тип конструктора. Некоторые из последствий — это динамическая отправка (вызовы build метода будут проходить через таблицу виртуальных функций), дополнительное выделение памяти (упакованный объект признака) и некоторые ограничения на признак ListBuilder .

 trait ListBuilder {
    fn build(amp;self, list: amp;mut Vec<String>);
}

struct Conf {
    list: Vec<String>,
    builder: Box<dyn ListBuilder>,
}

impl Conf {
    fn init(amp;mut self) {
        self.builder.build(amp;mut self.list);
    }
}

impl Conf {
    pub fn new<T: ListBuilder   'static>(lb: T) -> Self {
        let mut c = Conf {
            list: vec![],
            builder: Box::new(lb),
        };
        c.init();
        c
    }
}
  

Комментарии:

1. Спасибо, это было в значительной степени то, что мне было нужно. Моему ListBuilder также потребовалось реализовать Clone, что я решил с помощью метода, описанного здесь: users.rust-lang.org/t /…