如何匹配trait实现者

48

我有一个特质(Trait),它由一些结构体(Struct)实现。我想编写一个模式匹配(Pattern Match),以便可以处理每种可能的情况:

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),
    }
}

我遇到了这个编译错误:

(Playground)

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`
3个回答

52
抱歉,我不能。特质不支持向下转型 - 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),
    }
}

(Playground)

这是目前最简单的方法,应该始终优先考虑。

另一种方法是使用Any特性。它是从 trait 对象到常规类型的类型安全向下转换的工具:

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,因为特质继承不能与特质对象一起使用(例如,无法从Box<Base>转换为Base<Any>)。


我能在枚举中使用命名结构字段吗?我的真实结构包含许多带名称的字段和几个方法。 - Kai Sellgren
您可以将任意数据放入枚举变量中。例如:struct Foo { f: uint }; enum FooBar { EFoo(Foo) }。枚举还支持在其变体中添加字段(称为结构体变体):enum FooBar { Foo { f: uint } },尽管此功能是有条件的,这只是一种语法上的便利 - 结构体变体不是结构体,不能有方法。 - Vladimir Matveev
Rust 在运行时检查类型的能力 - 它通过 Any 特质公开。AnyRefExtis::<T>() 方法,本质上与 instanceof 相同。然而,Rust 不鼓励使用它,而是倡导静态类型检查。编写基于枚举的 match 分发要安全得多,因为它保证处理每种可能的情况,并且可以在静态上检查。 - Vladimir Matveev
1
@snuk182,如果你正在“获取BaseTrait的任意实例”,而且你无法更改它,那么显然枚举对你没有用。坦白地说,在这种情况下我不知道还能做什么 - Rust根本没有这样的向下转型功能。如果你能够更改它,你可以使用在枚举变体中存储trait对象的枚举(或者实际实现类型,如果你也能更改它)。 - Vladimir Matveev
是的,这正是@VladimirMatveev提出的方案,而且它工作得足够好。 - snuk182
显示剩余3条评论

8
您可以使用我的 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 },
});

20
当你推荐自己创作的东西时,透露这一点被认为是良好的礼仪。 - Shepmaster

5
我建议使用访问者模式来匹配特性。虽然这个模式源自面向对象编程,但它在很多情况下都非常出色。此外,它的效率更高,同时避免了向下转换。
以下是代码片段:
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{} ));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接