我能否使用特质对象进行类型内省,然后进行向下转型?

15

我有一个Trait集合,一个函数遍历它并执行某些操作,然后我想检查实现类型,如果是Foo类型,则将其向下转换并调用某些Foo方法。

基本上,类似于Go的类型开关接口转换

在搜索过程中,我发现了任何特性,但它只能在'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();
    //}
}

你确定需要向下转型吗?(应尽可能避免使用它)。如果你控制TraitFoo中的任意一个,你很可能可以避免使用它。 - Matthieu M.
这似乎就是枚举所设计的场景。 - goertzenator
@goertzenator,您能否提供一个示例?我遇到了几乎相同的问题,不太明白如何正确使用枚举来解决这个问题。先谢谢了。 - thamurath
2个回答

9
正如你已经注意到的那样,向下转换(downcasting)只适用于 Any 特质,并且仅支持 'static 数据。你可以在这里找到一个关于为什么会这样的最近讨论:http://www.reddit.com/r/rust/comments/2r25rf/why_does_any_only_work_for_t_static/。基本上,为任意生命周期的引用实现反射是困难的。
目前来说,将你的自定义特质与 Any 轻松地结合起来也是不可能的。但是,最近创建了一个用于自动实现你的特质 Any宏库。你也可以在这里找到有关它的一些讨论:http://www.reddit.com/r/rust/comments/2ricdw/mopa_my_own_personal_any_a_macro_to_implement_all/

3

虽然词汇可能有些不同,但这并不是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<dyn Trait>> = vec![Box::new(Foo), Box::new(Bar)];

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

在这个例子中,我在Trait中放置了一个默认实现的foo_method,它什么也不做,这样你就不必在每个impl中都定义它,只需要在适用的那些impl中定义即可。在采取向具体类型下转换之前,您应该尝试使上述方法工作,这是非常重要的,因为向具体类型下转换具有严重的缺点,几乎抵消了首次使用trait对象的优势。
也就是说,有些情况下可能需要向具体类型下转换,并且Rust支持它--虽然接口有点笨重。你可以通过添加一个中间的向上转换到任意类型&Any来将&Trait向&Foo的向下转换。
use std::any::Any;

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

struct Foo;

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

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

as_any必须是Trait中的方法,因为它需要访问具体类型。现在,你可以像这样在Trait特征对象上尝试调用Foo方法(完整的演示示例):

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

为了使其正常工作,您必须指定您期望的类型(::<Foo>),并使用if let处理引用对象不是Foo实例时的情况。除非您确切地知道具体类型,否则无法将一个trait对象向下转换为具体类型。
如果您需要知道具体类型,那么trait对象几乎没有用处!您应该使用enum,这样在某个地方省略处理变量时,将会得到编译时错误。此外,您无法将Any与非'static结构体一起使用。因此,如果任何Foo可能需要包含引用,则此设计是死路一条。如果可以的话,最好的解决方案是将foo_method添加到trait本身中。

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