【问题标题】:How to create Vec of references to generic trait objects in Rust? [duplicate]如何在 Rust 中创建对通用特征对象的引用的 Vec? [复制]
【发布时间】:2021-05-11 05:35:36
【问题描述】:

我是 Rust 的新手。我的背景是Java。我正在尝试解决以下问题。

  1. 我有一个为struct Dieselstruct Gas 实现的trait Fuel
  2. 我还有一个trait VehicleFuel 泛型类型参数。车辆为struct Car<F: Fuel>struct Bus<F: Fuel> 实现。

最后,我想将可能的异构 trait 对象的引用放到一个 Vec 中,如下所示:

let diesel_car = Car { fuel: Diesel {} };
let gas_car = Car { fuel: Gas {} };
let diesel_bus = Bus { fuel: Diesel {} };
let gas_bus = Bus { fuel: Gas {} };

let garage = vec![
    &diesel_car,
    &gas_car,
    &diesel_bus,
    &gas_bus
];

但是我得到这个编译错误:

error[E0308]: mismatched types
  --> src/main.rs:63:9
   |
63 |         &gas_car,
   |         ^^^^^^^^ expected struct `Diesel`, found struct `Gas`
   |
   = note:   expected type `&Car<Diesel>`
           found reference `&Car<Gas>`

这个 Vec 中引用的通用类型应该是什么?它猜它应该是Vec&lt;&amp;dyn Vehicle&gt; 之类的东西。但是这个变体也不能编译,因为它想提前知道Vehicle 的泛型类型参数。在 Java 中,我只会写 List&lt;Vehicle&lt;?&gt;。 Rust 中是否有类似的东西?

整个示例可在playground 中找到。

附:我显然可以从Vehicle 中删除一个通用参数并将其替换为Box&lt;dyn Fuel&gt;,但我想通过动态调度来最小化位置。

【问题讨论】:

标签: generics rust polymorphism traits trait-objects


【解决方案1】:

但是这个变体也不能编译,因为[编译器]想提前知道Vehicle的泛型类型参数。

我认为您在这里混淆了静态调度和动态调度。我认为你也混淆了你要求编译器做什么和你期望它做什么。听起来很明显你想要动态调度,但是你试图使用泛型来实现它,这是不可能的,因为泛型是一个纯粹的编译时抽象,并且总是会产生静态调度代码。 “泛型”在运行时不存在,也不存在于最终的二进制文件中。如果你想要“运行时泛型”,那就是 trait 对象和动态调度的用途。

在 Java 中我只会写 List&lt;Vehicle&lt;?&gt;&gt;。 Rust 中是否有类似的东西?

是的,将 F: Fuel 泛型类型参数替换为 dyn Fuel 特征对象。

附:我显然可以从Vehicle 中删除一个通用参数并将其替换为Box&lt;dyn Fuel&gt;,但我想通过动态调度来最小化位置。

但是您只是问如何在 Rust 中实现 Java 等价物,而这正是 Java 解决这个问题的方式:通过使用动态调度。你不能一边吃蛋糕一边吃。如果你想要静态调度的速度,那就意味着还要接受静态调度的约束。如果这些约束对您的程序来说过于严格,那么您应该使用 trait 对象和动态调度。

Rust 的枚举可能是最接近你想要的。它们是泛型和特征对象之间的一个很好的折衷方案,为您(几乎)提供了前者的速度,同时为您提供了后者的灵活性。这是重构为使用枚举的示例:

enum Fuel {
    Diesel,
    Gas,
}

impl Fuel {
    fn efficiency(&self) -> f64 {
        match self {
            Fuel::Diesel => 0.9,
            Fuel::Gas => 0.8,
        }
    }
}

enum Vehicle {
    Car(Fuel),
    Bus(Fuel)
}

impl Vehicle {
    fn mass(&self) -> f64 {
        match self {
            Vehicle::Car(_) => 1000.0,
            Vehicle::Bus(_) => 5000.0,
        }
    }
}

fn main() {
    let diesel_car = Vehicle::Car(Fuel::Diesel);
    let gas_car = Vehicle::Car(Fuel::Gas);
    let diesel_bus = Vehicle::Bus(Fuel::Diesel);
    let gas_bus = Vehicle::Bus(Fuel::Gas);

    let garage = vec![
        &diesel_car,
        &gas_car,
        &diesel_bus,
        &gas_bus
    ];
}

playground

【讨论】:

  • 谢谢。与我在实践中想要的相比,我的示例过于简化。在实践中,希望能够灵活地添加新燃料和车辆,因此使用枚举可能不是解决此问题的最佳方法。此外,与使用动态调度相比,它是否有任何性能提升?实际上,我们只是在 match 表达式中复制了动态调度的逻辑,不是吗?
  • 使用match 使您不必取消对vtable 指针的引用,因此我相信它比常规动态调度更快。此外,Java 对所有事情都使用动态分派和垃圾收集。因此,如果 Java 的速度足以满足您的需求,那么毫无疑问,即使您不使用静态调度,Rust 也会足够快。
【解决方案2】:

不可能将不同的类型放在同一个向量中。 但是,使用枚举,您可以在不需要不同类型的情况下获得您正在寻找的功能。

由于您有 2 种燃料,它们的枚举将如下所示:

pub enum Fuel {
    Diesel,
    Gas,
}

然后添加一个函数 efficiency 以返回每种燃料变体的效率:

impl Fuel {
    fn efficiency(&self) -> f64 {
        match self {
            Fuel::Diesel => 0.9,
            Fuel::Gas => 0.8,
        }
    }
}

您现在拥有 2 种相同类型的燃料。

对车辆执行相同操作后(您也有 2 个变体),您可以将各种组合添加到同一个向量。

Here is a full example.

You can read up on enums here.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-02
    • 2016-10-05
    相关资源
    最近更新 更多