Могу ли я сделать самоанализ типа с объектами черты и затем уменьшить его?

У меня есть коллекция Trait, функция, которая перебирает его и что-то делает, а затем я хотел бы проверить тип разработчика и, если он имеет тип Foo затем опусти его и вызови метод Foo.

По сути, что-то похожее на переключение типов и преобразование интерфейса Go.

В поисках я обнаружил черту Any, но ее можно реализовать только на 'static типы.

Чтобы помочь продемонстрировать, что я хочу:

let vec: Vec<Box<Trait>> = //

for e in vec.iter() {
    e.trait_method();

    // if typeof e == Foo {
    // let f = e as Foo;
    // f.foo_method();
    //}
}

2 ответа

Решение

Как вы заметили, downcasting работает только с Any черта, и да, это только поддерживает 'static данные. Вы можете найти недавнюю дискуссию о том, почему это так здесь. В принципе, реализовать рефлексию для ссылок произвольных времен жизни сложно.

Также невозможно (на данный момент, по крайней мере) объединить Any с вашей пользовательской чертой легко. Тем не менее, библиотека макросов для автоматической реализации Any для вашей черты недавно был создан. Вы также можете найти обсуждение этого здесь.

Это не специфичная для Rust проблема, хотя словарный запас может немного отличаться. Идеальный способ решить такую ​​проблему, не только с чертами в Rust, но и на любом языке, это добавить желаемое поведение (foo_method в вашем примере) в абстрактный интерфейс (Trait):

trait Trait {
    fn trait_method(&self);
    fn foo_method(&self) {} // does nothing by default
}

struct Foo;

impl Trait for Foo {
    fn trait_method(&self) {
        println!("In trait_method of Foo");
    }

    fn foo_method(&self) { // override default behavior
        println!("In foo_method");
    }
}

struct Bar;

impl Trait for Bar {
    fn trait_method(&self) {
        println!("In trait_method of Bar");
    }
}

fn main() {
    let vec: Vec<Box<Trait>> = vec![Box::new(Foo), Box::new(Bar)];

    for e in &vec {
        e.trait_method();
        e.foo_method();
    }
}

В этом примере я поместил реализацию по умолчанию foo_method в Trait который ничего не делает, так что вам не нужно определять его в каждом impl но только один (и), где он применяется. Вы должны действительно попытаться заставить вышеупомянутую работу работать, прежде чем прибегнуть к понижению в конкретном типе, что имеет серьезные недостатки, которые почти стирают преимущества наличия черт объектов в первую очередь.

Тем не менее, бывают случаи, когда даункастинг может быть необходим, и Rust поддерживает его - хотя интерфейс немного неуклюжий. Вы можете опустошить &Trait в &Foo добавив промежуточный актерский состав к &Any:

use std::any::Any;

trait Trait {
    fn as_any(&self) -> &Any;
}

struct Foo;

impl Trait for Foo {
    fn as_any(&self) -> &Any {
        self
    }
}

fn downcast<T: Trait + 'static>(this: &Trait) -> Option<&T> {
    this.as_any().downcast_ref()
}

as_any должен быть метод в Trait потому что ему нужен доступ к конкретному типу. Теперь вы можете попытаться позвонить Foo методы на Trait Подобный объект ( полный пример игровой площадки):

if let Some(r) = downcast::<Foo>(trait_object_ref) {
    r.foo_method();
}

Чтобы это работало, нужно указать, какой тип вы ожидаете (::<Foo>) и использовать if let обрабатывать то, что происходит, когда указанный объект не является экземпляром Foo, Вы не можете понизить объект черты до конкретного типа, если точно не знаете, какой это конкретный тип.

Но если вам когда-нибудь понадобится узнать конкретный тип, черты объектов практически бесполезны! Вы, вероятно, должны использовать enum вместо этого, так что вы получите ошибки во время компиляции, если вы опустите обработку где-то варианта. Кроме того, вы не можете использовать Any с не 'static структуры, так что если таковые имеются Foo может потребоваться ссылка, этот дизайн тупик. Лучшее решение, если вы можете сделать это, это добавить foo_method на саму черту.

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