Вектор полиморфных структур со связанным типом признака

Я пытаюсь понять, как работает полиморфизм при использовании характеристики со связанным типом. Обратите внимание на следующую черту:

trait Animal {
    fn talk (&self);
}

Эта черта используется следующими структурами:

struct Dog;
struct Cow;

impl Animal for Dog {
    fn talk (&self) {
        println!("Woof");
    }
}

impl Animal for Cow {
    fn talk (&self) {
        println!("Moo");
    }
}

Затем я перебираю Vec<&Animal>, и здесь хорошо работает полиморфизм:

fn main() {
    let animals: Vec<&Animal> = vec![&Dog, &Cow];
    for animal in &animals {
        animal.talk();
    }
}
// output:
// Woof
// Moo

Все идет нормально. Теперь я добавляю связанный тип Food к характеристике (тип не используется, но только для минимального воспроизведения).

struct Meat;
struct Herb;

trait Animal {
    type Food;
    ...
}
impl Animal for Dog {
    type Food = Meat;
    ...
}
impl Animal for Cow {
    type Food = Herb;
    ...
}

И теперь я получаю ошибку:

error[E0191]: the value of the associated type `Food` (from trait `Animal`) must be specified
   --> src/main.rs:188:23
163 |     type Food;
    |     ---------- `Food` defined here
...
188 |     let animals: Vec<&Animal> = vec![&Dog, &Cow];
    |                       ^^^^^^ help: specify the associated type: `Animal<Food = Type>`

Но в этом случае я не могу просто следить за сообщением об ошибке, поскольку количество struct, реализующих черту Animal, не должно быть статическим.

Как это решить с помощью Rust? заранее спасибо


person Loheek    schedule 22.04.2021    source источник
comment
Вы, вероятно, ищете enum Food { Meat, Herb } с fn food(&self) -> Food в вашей характеристике.   -  person eggyal    schedule 22.04.2021
comment
Я все еще изучаю Rust, но не думаю, что могу здесь преобразовать Meat и Herb в перечисления. Это минимальное воспроизведение, на самом деле это очень сложные конструкции. Мой вопрос был не об адаптации кода, а об использовании полиморфных векторов в ржавчине со связанными типами в целом. Но может я неправильно понял твой ответ   -  person Loheek    schedule 22.04.2021
comment
Единственная причина, по которой вы предпочитаете типажные объекты перечислениям, заключается в том, что вы пишете библиотеку, нижестоящие пользователи которой могут создавать реализации, о которых вы не можете знать заранее, но это происходит за счет как производительности во время выполнения, так и безопасности объекта, налагая дополнительные ограничения, в том числе указание конкретных значений для любых связанных типов.   -  person eggyal    schedule 22.04.2021
comment
Спасибо. Я, вероятно, заблуждаюсь относительно силы перечислений ржавчины, но здесь часть кода, которая выполняет полиморфный вызов, не знает о различных типах животных. Как я могу использовать шаблон match, не зная различных типов, чтобы использовать правильную функцию? Это не столько о пользователях, я просто не хочу копировать / вставлять весь список типов везде, когда я добавляю или удаляю тип   -  person Loheek    schedule 22.04.2021
comment
Если вас интересует только один вариант, рассмотрите if let. В противном случае вы можете использовать match с подстановочным знаком _ для игнорирования вариантов, которые вам не нужны.   -  person eggyal    schedule 22.04.2021
comment
Мой основной код - это конкретный пример. В main я не хочу копировать / вставлять в шаблон match каждый тип, реализующий эту черту, именно поэтому полиморфизм вообще полезен.   -  person Loheek    schedule 22.04.2021
comment
Но что вы хотите делать с пищей для животных в main? Возможно, вам стоит вместо этого реализовать метод на Food?   -  person eggyal    schedule 22.04.2021
comment
Я не думаю, что Food функции входят в объем этой проблемы, проблема в том, что полиморфизм работает хорошо, у меня есть main функция, когда я могу вызвать функцию подтипа talk() без копирования / вставки 1435 имен типов животных. Но если у признака есть связанный тип (это не то, что я могу изменить), он больше не работает. Я хотел бы сохранить этот anything.talk(), который вызывал функцию правильного подтипа, которая сработала в первый раз. Я был бы удивлен, что нет способа сделать это   -  person Loheek    schedule 22.04.2021
comment
Нет, Food действительно проблема здесь. Кроме того, у вас вообще никогда не было полиморфизма, потому что Animal или, скорее, dyn Animal, как должен сказать вам компилятор, является определенным, конкретным типом. Когда вы добавляете к нему связанный тип, он вместо этого становится бесконечным набором dyn Animal<Food = T> типов.   -  person Ivan C    schedule 22.04.2021
comment
Объекты признаков без явного dyn все еще работают с моей версией. Но спасибо, что напомнили мне, что это не рекомендуется. Однако проблема все та же, я не могу удалить связанный тип и хотел бы иметь уникальный anything.talk(). Итак, если я хорошо понимаю, вы говорите мне, что в Rust это в принципе невозможно.   -  person Loheek    schedule 22.04.2021


Ответы (1)


&Animal - это сокращение от &dyn Animal. dyn Animal - это тип признака, и он существует только для данного признака, если признак object-safe. Черты со связанными типами небезопасны для объектов, потому что dyn Animal не может реализовать Animal без указания связанного типа Food.

Это неотъемлемое ограничение полиморфизма времени выполнения (типажных объектов): вы не знаете конкретный тип, поэтому вы не можете знать связанный с ним тип².

Если вы хотите создать вектор вещей, которые можно вызывать .talk(), достаточно просто создать черту только для этого (площадка):

trait Talk {
    fn talk(&self);
}

impl<A: Animal> Talk for A {
    fn talk(&self) {
        Animal::talk(self);
    }
}

let animals: Vec<&dyn Talk> = vec![&Dog, &Cow];

Вы не сможете написать код, который использует Food через &dyn Talk, в чем суть: Food зависит от конкретного типа, а ваш вектор содержит несколько конкретных типов.

Смотрите также


¹ Вы можете создать объект-признак из типов, у всех которых есть одинаковые связанные типы, например dyn Animal<Food = Herb>. Обычно это наблюдается с Iterator, как и с Box<dyn Iterator<Item = i32>>. Но это не решает проблемы, когда Animal имеют разные типы Food.

² С помощью полиморфизма времени компиляции (обобщения) вы можете написать код, который является универсальным для всего, что реализует Animal с любым Food типом. Но вы не можете поместить в Vec объекты разных типов времени компиляции, так что это тоже не помогает.

person trentcl    schedule 22.04.2021