【问题标题】:How to match trait implementors如何匹配特征实现者
【发布时间】:2014-09-30 17:28:57
【问题描述】:

我有一个由某些结构实现的特征。我想写一个模式匹配,我可以处理所有可能的情况:

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`

【问题讨论】:

    标签: rust


    【解决方案1】:

    你不能。 Traits 不支持向下转换 - 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。它是一种从 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!() }
    }
    

    (Playground)

    理想情况下应该可以这样写:

    trait Base: Any {}
    
    impl Base for Foo {}
    impl Base for Bar {}
    

    然后在代码中使用Base,但现在不能这样做,因为特征继承不适用于特征对象(例如,不可能从Box&lt;Base&gt;转到Base&lt;Any&gt;)。

    【讨论】:

    • 我可以将命名结构字段与枚举一起使用吗?我真正的结构包含许多带有名称和方法的字段。
    • 您可以将任意数据放入枚举变量中。这将起作用,例如:struct Foo { f: uint }; enum FooBar { EFoo(Foo) }。枚举还支持其变体中的字段(称为结构变体):enum FooBar { Foo { f: uint } },尽管此功能是封闭的,这只是为了语法方便 - 例如,结构变体不是结构,不能有方法。
    • 嗯,Rust具有在运行时检查类型的能力——它通过Any trait 暴露出来。 AnyRefExt 具有 is::&lt;T&gt;() 方法,它与 instanceof 基本相同。然而,Rust 不鼓励它支持静态类型检查。编写基于枚举的 match 调度更安全,因为它可以保证处理所有可能的情况并且可以静态检查。
    • 我有特征的层次结构(BaseTrait -> [ChildTrait1, ChildTrait2,... ChildTraitN])。子特征的实现结构没有公开,我只能通过特征进行操作。我正在获取 BaseTrait 的任意实例,并且需要获取其确切的 ChildTrait# 来对其进行强制转换并执行 ChildTrait# 的特定操作?枚举匹配是否适用于我的情况?
    • @snuk182,如果您“获取 BaseTrait 的任意实例,并且您无法更改它,那么显然枚举对您不起作用。坦率地说,我不知道可以是什么在这种情况下完成 - Rust 根本不具备这种向下转换的能力。如果你能够改变它,你可以使用带有存储在枚举变体中的特征对象的枚举(或者使用实际的实现类型,如果你也可以改变)。
    【解决方案2】:

    你可以使用我的match_cast crate:

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

    【讨论】:

    【解决方案3】:

    我建议使用访客模式来匹配特征。这种模式来自 OOP,但它在许多情况下都大放异彩。此外,它更有效,同时避免了向下转换。

    这是一个这样的sn-p:

    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{} ));
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-07
      • 1970-01-01
      • 2021-11-14
      • 1970-01-01
      • 2015-08-15
      • 1970-01-01
      相关资源
      最近更新 更多