【问题标题】:Can I do type introspection with trait objects and then downcast it?我可以使用特征对象进行类型自省然后向下转换吗?
【发布时间】:2015-01-11 21:35:00
【问题描述】:

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

基本上,类似于 Go 的 type-switchinterface conversion

四处搜索我发现了Any trait,但它只能在'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,你很可能会避免它。
  • 这似乎是 Enums 的场景。
  • @goertzenator 你能举个例子吗?我面临着几乎同样的问题,不明白如何正确使用枚举来解决这个问题。提前致谢

标签: rust


【解决方案1】:

正如您所注意到的,向下转换仅适用于 Any 特征,是的,它仅支持 'static 数据。您可以找到最近关于为什么会如此的讨论 here。基本上,为任意生命周期的引用实现反射是很困难的。

也不可能(至少到目前为止)将Any 与您的自定义特征轻松结合起来。但是,最近创建了一个 macro library 用于自动实现您的 trait 的 Any。你也可以在here找到一些讨论。

【讨论】:

    【解决方案2】:

    这不是 Rust 特有的问题,尽管词汇表可能有点不同。解决此类问题的理想方法,不仅仅是使用 Rust 中的特征,而是使用任何语言,是将所需的行为(在您的示例中为 foo_method)添加到抽象接口(@98​​7654323@):

    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 中定义它,而只需在它适用的那个(S)中定义它.您应该真的尝试在向下转换为具体类型之前完成上述工作,这具有严重的缺点,几乎完全消除了首先具有特征对象的优点。

    也就是说,在某些情况下可能需要向下转换,Rust 确实支持它——尽管接口有点笨拙。您可以通过向&amp;Any 添加中间向上转换来将&amp;Trait 向下转换为&amp;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 中的一个方法,因为它需要访问具体类型。现在你可以尝试像这样 (complete playground example) 对 Trait 特征对象调用 Foo 方法:

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

    要完成这项工作,您必须指定您期望的类型 (::&lt;Foo&gt;) 并使用 if let 来处理当引用的对象不是 Foo 的实例时发生的情况。除非您确切知道 what 它是具体类型,否则您不能将 trait 对象向下转换为具体类型。

    如果您需要知道具体类型,那么 trait 对象几乎是无用的!您可能应该改用enum,这样如果您忽略在某处处理变体,您将得到编译时错误。此外,您不能将Any 与非'static 结构一起使用,因此如果任何Foo 可能需要包含引用,则此设计是死路一条。如果可以的话,最好的解决方案是将foo_method 添加到特征本身。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-11
      • 2011-12-13
      • 1970-01-01
      • 2020-01-27
      • 1970-01-01
      • 1970-01-01
      • 2017-07-31
      相关资源
      最近更新 更多