Как соответствовать чертам разработчиков
У меня есть черта, которая реализована некоторыми структурами. Я хочу написать сопоставление с образцом, где я могу обработать каждый возможный случай:
trait Base {}
struct Foo { x: uint }
struct Bar { y: uint }
impl Base for Foo {}
impl Base for Bar {}
fn test(v: bool) -> Box<Base + 'static> {
// Let's pretend there's real logic that determines what to return.
if v {
box Foo { x: 5 }
} else {
box Bar { y: 10 }
}
}
fn main() {
let f: Box<Base> = test(true);
// Now that we have a `Box<Base>` (`*f` makes it a `Base`),
// let's handle different cases:
match *f {
Foo { x } => println!("it was Foo: {}!", x),
Bar { y } => println!("it was Bar: {}!", y),
}
}
(попробуйте онлайн: http://is.gd/YuBkPF)
Я получаю эту ошибку компиляции:
несоответствующие типы: ожидается
Base
, нашел структуру структуры
2 ответа
Ты не можешь Черты не поддерживают downcasting - Rust не является языком, основанным на наследовании / подтипах, и дает вам другой набор абстракций. Более того, то, что вы хотите сделать, является необоснованным - черты открыты (каждый может реализовать их для чего угодно), так что даже если в вашем случае match *f
охватывает все возможные случаи, в общем, компилятор не может этого знать.
У вас есть два варианта здесь. Если вы знаете множество структур, реализующих вашу черту заранее, просто используйте enum, это идеальный инструмент для этого. Они позволяют статически сопоставлять закрытый набор вариантов:
enum FooBar {
Foo(uint),
Bar(uint)
}
fn test(v: bool) -> FooBar {
if v {
Foo(5)
} else {
Bar(10)
}
}
fn main() {
let f: FooBar = test(true);
// Now that we have a `Box<Base>` (`*f` makes it a `Base`),
// let's handle different cases:
match f {
Foo(x) => println!("it was Foo: {}!", x),
Bar(y) => println!("it was Bar: {}!", y),
}
}
(попробуйте здесь)
Это, безусловно, самый простой способ, и он всегда должен быть предпочтительным.
Другой способ заключается в использовании Any
черта характера. Это средство для безопасной передачи типов с характерных объектов на обычные типы:
use std::any::{Any, AnyRefExt};
struct Foo { x: uint }
struct Bar { y: uint }
fn test(v: bool) -> Box<Any + 'static> { // '
if v {
box Foo { x: 5 }
} else {
box Bar { y: 10 }
}
}
fn main() {
let f: Box<Any> = test(true);
match f.downcast_ref::<Foo>() {
Some(&Foo { x }) => println!("it was Foo: {}!", x),
None => match f.downcast_ref::<Bar>() {
Some(&Bar { y }) => println!("it was Bar: {}!", y),
None => unreachable!()
}
}
// it will be nicer when `if let` lands
// if let Some(&Foo { x }) = f.downcast_ref::<Foo>() {
// println!("it was Foo: {}!", x);
// } else if let Some(&Bar { y }) = f.downcast_ref::<Bar>() {
// println!("it was Bar: {}!", y);
// } else { unreachable!() }
}
(попробуйте здесь)
В идеале должно быть возможно написать что-то вроде этого:
trait Base : Any {}
impl Base for Foo {}
impl Base for Bar {}
а затем использовать Base
в коде, но это не может быть сделано сейчас, потому что наследование признаков не работает с объектами признаков (например, невозможно перейти от Box<Base>
в Base<Any>
).
Вы можете использовать мой match_cast
обрешетка:
match_cast!( any {
val as Option<u8> => {
format!("Option<u8> = {:?}", val)
},
val as String => {
format!("String = {:?}", val)
},
val as &'static str => {
format!("&'static str = {:?}", val)
},
});
match_down!( any {
Bar { x } => { x },
Foo { x } => { x },
});
Я предлагаю шаблон посетителя соответствовать по признакам. Это шаблон из ООП, но он эффективен во многих ситуациях. Более того, это более эффективно, избегая при этом понижающего преобразования.
Вот такой фрагмент:
struct Foo{ value: u32 }
struct Bar{ value: u32 }
trait Base<T> {
fn accept(&self, v: &dyn Visitor<Result = T>) -> T ;
}
impl <T>Base<T> for Foo {
fn accept(&self, v: &dyn Visitor<Result = T>) -> T {
v.visit_foo(&self)
}
}
impl <T>Base<T> for Bar {
fn accept(&self, v: &dyn Visitor<Result = T>) -> T {
v.visit_bar(&self)
}
}
trait Visitor {
type Result;
fn visit_foo(&self, foo: &Foo) -> Self::Result;
fn visit_bar(&self, bar: &Bar) -> Self::Result;
}
struct StringVisitor {}
impl Visitor for StringVisitor {
type Result = String;
fn visit_foo(&self, foo: &Foo) -> String {
format!("it was Foo: {:}!", foo.value)
}
fn visit_bar(&self, bar: &Bar) -> String {
format!("it was Bar: {:}!", bar.value)
}
}
fn test<T>(v: bool) -> Box<dyn Base<T>> {
if v {
Box::new(Foo{value: 5})
} else {
Box::new(Bar{value: 10})
}
}
fn main() {
let f = test(true);
println!("{:}", f.accept( &StringVisitor{} ));
}