У меня есть ситуация, когда несколько моих структур должны реализовывать несколько трейтов, но все они реализуют по крайней мере одну общую черту. Когда я получаю смешанный набор этих структур, я хочу рассматривать их все как имеющие общую черту: передавать их как параметры метода, типизированные для этой черты, хранить их в коллекциях, типизированных для этой черты, и т. Д.

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

trait ThingWithKeys {
    fn use_keys (&self) -> String;
}

//////

trait CorrectionsOfficer {
    fn hitch_up_pants (&self) -> String;
}

trait CorrectionsOfficerWithKeys: ThingWithKeys + CorrectionsOfficer {}

struct CorrectionsOfficerReal {}

impl ThingWithKeys for CorrectionsOfficerReal {
    fn use_keys (&self) -> String {
        String::from ("Clank, clank")
    }
}

impl CorrectionsOfficer for CorrectionsOfficerReal {
    fn hitch_up_pants (&self) -> String {
        String::from ("Grunt")
    }
}

impl <T: ThingWithKeys + CorrectionsOfficer> CorrectionsOfficerWithKeys for T {}

//////

trait Piano {
    fn close_lid (&self) -> String;
}

trait PianoWithKeys: Piano + ThingWithKeys {}

struct PianoReal {}

impl ThingWithKeys for PianoReal {
    fn use_keys (&self) -> String {
        String::from ("Tinkle, tinkle")
    }
}

impl Piano for PianoReal {
    fn close_lid (&self) -> String {
        String::from ("Bang!")
    }
}

impl <T: ThingWithKeys + Piano> PianoWithKeys for T {}

//////

trait Florida {
    fn hurricane (&self) -> String;
}

trait FloridaWithKeys: ThingWithKeys + Florida {}

struct FloridaReal {}

impl ThingWithKeys for FloridaReal {
    fn use_keys (&self) -> String {
        String::from ("Another margarita, please")
    }
}

impl Florida for FloridaReal {
    fn hurricane (&self) -> String {
        String::from ("Ho-hum...")
    }
}

impl <T: ThingWithKeys + Florida> FloridaWithKeys for T {}

//////

fn main() {
    let corrections_officer_ref: &CorrectionsOfficerWithKeys = &CorrectionsOfficerReal {};
    let piano_ref: &PianoWithKeys = &PianoReal {};
    let florida_ref: &FloridaWithKeys = &FloridaReal {};

    use_keys (corrections_officer_ref);
    use_keys (piano_ref);
    use_keys (florida_ref);
}

fn use_keys (thing_with_keys: &ThingWithKeys) {
    println! ("{}", thing_with_keys.use_keys ());
}

Вот ошибки компиляции:

Compiling playground v0.0.1 (file:///playground)
error[E0308]: mismatched types
  --> src/main.rs:80:19
   |
80 |         use_keys (corrections_officer_ref);
   |                   ^^^^^^^^^^^^^^^^^^^^^^^ expected trait `ThingWithKeys`, found trait `CorrectionsOfficerWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&CorrectionsOfficerWithKeys`

error[E0308]: mismatched types
  --> src/main.rs:81:19
   |
81 |         use_keys (piano_ref);
   |                   ^^^^^^^^^ expected trait `ThingWithKeys`, found trait `PianoWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&PianoWithKeys`

error[E0308]: mismatched types
  --> src/main.rs:82:19
   |
82 |         use_keys (florida_ref);
   |                   ^^^^^^^^^^^ expected trait `ThingWithKeys`, found trait `FloridaWithKeys`
   |
   = note: expected type `&ThingWithKeys`
              found type `&FloridaWithKeys`

error: aborting due to 3 previous errors

По сути, он все еще не может найти реализацию ThingWithKeys внутри реализаций XxxWithKeys.

13
Dan Wiebe 25 Дек 2017 в 08:36

1 ответ

Лучший ответ

Наследование признаков в Rust отличается от наследования ООП. Наследование признаков - это просто способ указать требования. trait B: A не означает , что если тип реализует B, он автоматически реализует A; это означает, что если тип реализует B, он должен реализовывать A. Это также означает, что вам придется реализовать A отдельно , если реализован B.

В качестве примера,

trait A {}
trait B: A {}

struct S;

impl B for S {}

// Commenting this line will result in a "trait bound unsatisfied" error
impl A for S {}

fn main() {
    let _x: &B = &S;
}

Однако, если вы хотите, чтобы тип автоматически реализовывал C, если он реализует A и B (и, таким образом, избегая ручной реализации C для этого типа), то вы можете использовать универсальный impl:

impl<T: A + B> C for T {}

В вашем примере это переводится как

impl<T: Florida + ThingWithKeys> FloridaWithKeys for T {}

Дополнительную информацию можно найти в этой ветке форума.

Кстати, вам не требуется граница ThingWithKeys для PianoWithKeys, поскольку Piano уже требует ThingWithKeys.

ИЗМЕНИТЬ (в соответствии с вашим комментарием и редактированием вопроса):

Как указывалось ранее, наследование признаков в Rust отличается от наследования ООП. Даже если trait B: A, вы не можете принудить объект признака B к объекту признака A . Если у вас нет другого выбора, кроме как передать объекты признаков как есть в метод, использование универсальных шаблонов работает:

fn use_keys<T: ThingWithKeys + ?Sized>(thing_with_keys: &T) {
    println! ("{}", thing_with_keys.use_keys ());
}

Универсальный метод будет работать и для ссылок на типы (объекты без признаков).

Также проверьте: Почему Rust не поддерживает повышение качества типажных объектов?

22
Shepmaster 5 Ноя 2018 в 22:11