能否将一个trait 对象转换成另一个trait对象?

10

我尝试了以下代码:

trait TraitA {
    fn say_hello(&self) {
        self.say_hello_from_a();
    }
    fn say_hello_from_a(&self);
}

trait TraitB {
    fn say_hello(&self) {
        self.say_hello_from_b();
    }
    fn say_hello_from_b(&self);
}

struct MyType {}

impl TraitA for MyType {
    fn say_hello_from_a(&self) {
        println!("Hello from A");
    }
}

impl TraitB for MyType {
    fn say_hello_from_b(&self) {
        println!("Hello from B");
    }
}

fn main() {
    let a: Box<dyn TraitA> = Box::new(MyType {});
    let b: Box<dyn TraitB>;

    a.say_hello();
    b = a;
    b.say_hello();
}

我遇到了以下编译错误:

error[E0308]: mismatched types
  --> src/main.rs:34:9
   |
34 |     b = a;
   |         ^ expected trait `TraitB`, found trait `TraitA`
   |
   = note: expected struct `std::boxed::Box<dyn TraitB>`
              found struct `std::boxed::Box<dyn TraitA>`

我声明了两个特质和一个名为MyType的类型,并为MyType实现了这两个特质。我创建了一个名为aMyType类型的新特质对象TraitA。由于a还实现了TraitB,所以我认为它应该能够被强制转换为TraitB

我还没有确定是否可能。如果可能的话,如何将特质对象a转换为TraitB

在C ++中,我会使用类似于std::dynamic_pointer_cast<TraitB>(a);的东西来实现相同的目的。

下面是一个我可以使用侧向转换的示例:我有一个包含一些数据的结构体,表示某个真实存在的实体:

struct MyType {
    a: i32,
    b: i32,
}

这种类型的实例可以在代码库的至少两个不同部分中使用。在这两个部分中,我需要一个名为get_final_value的行为。

有趣的是,get_final_value应该根据调用它的对象产生不同的响应。

  • 为什么不将类型拆分为两个不同的类型?从技术上讲,按设计,ab是相互关联的,更不用说get_final_value()需要使用两个值来计算结果了。

  • 为什么不使用泛型/静态分发?因为MyType只是一个示例。在真实情况下,我有不同的结构体,它们都以不同的方式实现了这两个特征。

  • 为什么不使用Any特征?老实说,我最近才知道它的存在。我不记得《Rust编程语言》提到过它。无论如何,似乎您需要知道具体类型才能将Any强制转换为该具体类型,然后再转换为特征对象。


1
你真的会在 C++ 中使用 dynamic_cast 进行横向转换吗?我可以理解你可以将其用于向下转换为具体类型,但我从未遇到过一个横向转换的实际应用场景。值得一提的是,可以使用 Any 特性来实现向下转换。 - Sven Marnach
你能详细说明一下你如何使用这些值以及你想如何在 get_final_value 中使用它们吗?例如,一种选择是让 get_final_value 接受任何实现了第三个 trait 的东西,然后 TraitATraitB 都可以公开一个将它们转换为该第三种类型的函数。 - loganfsmyth
关于Any的问题 - 这个代码使用了内部存在于Any中的代码来执行向下转型。Rust仍然不原生支持向下转型,这是一个独立的事情。编写Any的人实际上编写了一些Rust代码来模拟向下转型,因为它不被支持。此外,Rust支持从类型到Trait对象的向上转型(普通指针到胖指针)。至于其他形式的向上转型,比如Trait对象之间的转型,我还没有详细研究过,所以无法给出答案。 - FreelanceConsultant
关于Any的观点-这使用了存在于Any内部的代码来执行向下转型。Rust仍然不原生支持向下转型,这是一个独立的事情。编写Any的人本质上编写了一些Rust代码来模拟向下转型,因为它不受支持。此外,Rust支持从类型向Trait对象的向上转型(普通指针到Fat指针)。我无法对其他形式的向上转型做出评论,比如Trait对象之间的转型,因为我还没有详细研究过。 - undefined
2个回答

6

另一种选择是创建一个特质,将TraitATraitB作为超特质,并为每种类型提供转换:

trait TraitC: TraitA + TraitB {
    fn as_trait_a(&self) -> &dyn TraitA;
    fn as_trait_b(&self) -> &dyn TraitB;
}

然后让 MyType 实现它:
impl TraitC for MyType {
    fn as_trait_a(&self) -> &dyn TraitA {
        self
    }
    fn as_trait_b(&self) -> &dyn TraitB {
        self
    }
}

完成上述操作后,您可以在Box中使用TraitC,并使用同时使用TraitATraitB的程序逻辑。

以下是展示各种使用方式的示例主函数:

fn test_a(a: &TraitA) {
    a.say_hello();
}
fn test_b(b: &TraitB) {
    b.say_hello();
}

fn main() {
    let c: Box<dyn TraitC> = Box::new(MyType {});

    TraitA::say_hello(&*c);
    TraitB::say_hello(&*c);

    c.as_trait_a().say_hello();
    c.as_trait_b().say_hello();

    test_a(c.as_trait_a());
    test_b(c.as_trait_b());

    let a: &dyn TraitA = c.as_trait_a();
    a.say_hello();
    let b: &dyn TraitB = c.as_trait_b();
    b.say_hello();
}

Rust Playground

如果AB真的属于一起,那么这个更好地代表了这一点,并且如果需要,仍然可以分别使用它们。


显然,这并不总是有效的。例如,Arc::downcast<T>(self) 要求 T 必须是 dyn Any + Send + Sync,不允许来自超级特征的 trait 对象。至少我不知道如何做到。 - stimulate
这很奇怪,因为继承层次通常会随着向下变得“更宽”,而不是更窄。这让我觉得使用这种模式会导致各种菱形继承问题,尽管我还不能确定。对我来说,这感觉不对劲。 - FreelanceConsultant
这很奇怪,因为继承层次通常会随着向下变得“更宽”,而不是更窄。这让我觉得使用这种模式会导致各种菱形继承问题,尽管我还不能确定。对我来说,这种感觉不对劲。 - undefined

2
使用 `Box<MyType>` 而不是 `Box<dyn Trait>` 可以解决这个问题。
fn main() {
    let a = Box::new(MyType {});

    TraitA::say_hello(&*a);
    TraitB::say_hello(&*a);
}

在这种情况下,不需要使用特质对象。Rust与C++有不同的范例。在大多数情况下,你通常可以使用泛型类型来解决问题。 如果你的问题确实适合使用特质对象来解决,你可以参考本书中的OOP章节

我知道我可以直接使用MyType。我的问题更多地涉及到当您已经拥有一个trait对象并希望将其用作不同的trait对象的情况。 - dospro
1
你可以使用Any来进行类型转换。但是你能举出一个必须使用它的场景吗? - Peng Guanwen
我在原始问题中添加了额外的上下文,以描述更加现实的情境。 - dospro

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