Могу ли я сделать самоанализ типа с объектами черты и затем уменьшить его?
У меня есть коллекция 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
на саму черту.