我有一个由某些结构实现的特征。我想写一个模式匹配,我可以处理每个可能的情况:
trait Base {}
struct Foo {
x: u32,
}
struct Bar {
y: u32,
}
impl Base for Foo {}
impl Base for Bar {}
fn test(v: bool) -> Box<Base + 'static> {
if v {
Box::new(Foo { x: 5 })
} else {
Box::new(Bar { y: 10 })
}
}
fn main() {
let f: Box<Base> = test(true);
match *f {
Foo { x } => println!("it was Foo: {}!", x),
Bar { y } => println!("it was Bar: {}!", y),
}
}
我收到此编译错误:
error[E0308]: mismatched types
--> src/main.rs:25:9
|
25 | Foo { x } => println!("it was Foo: {}!", x),
| ^^^^^^^^^ expected trait Base, found struct `Foo`
|
= note: expected type `dyn Base`
found type `Foo`
error[E0308]: mismatched types
--> src/main.rs:26:9
|
26 | Bar { y } => println!("it was Bar: {}!", y),
| ^^^^^^^^^ expected trait Base, found struct `Bar`
|
= note: expected type `dyn Base`
found type `Bar`
答案 0 :(得分:25)
你做不到。特征不支持向下转换 - Rust不是基于继承/子类型的语言,它为您提供了另一组抽象。此外,你想做的事情是不健全的 - 特征是开放的(每个人都可以为任何事情实现它们),所以即使在你的情况下match *f
涵盖所有可能的情况,通常编译器也不能知道。
这里有两种选择。如果您事先知道实现特征的结构集,只需使用枚举,它就是一个完美的工具。它们允许您在一组封闭的变体上进行静态匹配:
enum FooBar {
Foo(u32),
Bar(u32),
}
fn test(v: bool) -> FooBar {
if v {
FooBar::Foo(5)
} else {
FooBar::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 {
FooBar::Foo(x) => println!("it was Foo: {}!", x),
FooBar::Bar(y) => println!("it was Bar: {}!", y),
}
}
这是迄今为止最简单的方法,应始终优先考虑。
另一种方法是使用Any
特征。它是从特征对象到常规类型的类型安全向下转换的工具:
use std::any::Any;
struct Foo {
x: u32,
}
struct Bar {
y: u32,
}
fn test(v: bool) -> Box<Any + 'static> {
if v {
Box::new(Foo { x: 5 })
} else {
Box::new(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(ref Foo { x }) = f.downcast_ref::<Foo>() {
// println!("it was Foo: {}!", x);
// } else if let Some(ref 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
,但现在无法完成,因为trait继承不适用于trait对象(例如,从Box<Base>
到{{1}是不可能的})。
答案 1 :(得分:3)
您可以使用我的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 },
});
答案 2 :(得分:1)
我建议访客模式要与特征匹配。这种模式来自OOP,但在许多情况下仍然很有用。而且,它在避免向下转换的同时效率更高。
这是一个摘要:
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{} ));
}