【问题标题】:Use Trait as Vec Type使用 Trait 作为 Vec 类型
【发布时间】:2017-09-06 00:33:15
【问题描述】:

我是 Rust 的新手,并且看到了一些使用 Box 来允许将许多实现特定 Trait 的类型推送到 Vec 的示例。当使用带有泛型的特征时,我遇到了一个问题。

error[E0038]: the trait `collision::collision_detection::Collidable` cannot be made into an object
  --> src/collision/collision_detection.rs:19:5
   |
19 |     collidables: Vec<Box<Collidable<P, M>>>,
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `collision::collision_detection::Collidable` cannot be made into an object
   |
   = note: method `get_ncollide_shape` has generic type parameters

error: aborting due to previous error

error: Could not compile `game_proto`.

To learn more, run the command again with --verbose.

这是我的代码

extern crate ncollide;
extern crate nalgebra as na;

use self::ncollide::shape::Shape;
use self::ncollide::math::Point;
use self::ncollide::math::Isometry;
use self::na::Isometry2;

pub trait Collidable<P: Point, M> {
    fn get_ncollide_shape<T: Shape<P, M>>(&self) -> Box<T>;
    fn get_isometry(&self) -> Isometry2<f64>;
}

pub struct CollisionRegistry<P, M>
where
    P: Point,
    M: Isometry<P>,
{
    collidables: Vec<Box<Collidable<P, M>>>,
}

impl<P: Point, M: Isometry<P>> CollisionRegistry<P, M> {
    pub fn new() -> Self {
        let objs: Vec<Box<Collidable<P, M>>> = Vec::new();
        CollisionRegistry { collidables: objs }
    }

    pub fn register<D>(&mut self, obj: Box<D>)
    where
        D: Collidable<P, M>,
    {
        self.collidables.push(obj);
    }
}

我正在尝试将 collidables 用作异构游戏对象的列表,这将使我可以将 ncollide 兼容的形状返回到碰撞检测引擎中。

编辑: 澄清一些混乱。我不是想构造和返回一个 Trait 的实例。我只是想创建一个 Vec,它允许将 Collidable 特征的任何实例推送到它上面。

【问题讨论】:

  • Why can a trait not construct itself? 的可能重复项可能有更好的重复项,但类似。
  • @loganfsmyth 这不是我想要做的。我已经阅读了大多数与此类似的示例,并通过使用 Vec> 让它们工作。但是当我使用具有像 Vec>> 这样的泛型类型的 trait 时,突然出现这个错误。
  • 您希望get_ncollide_shape 做什么?错误是因为Box&lt;Collidable&lt;P, M&gt;&gt; 意味着基本上你已经删除了关于一个对象的所有数据,除了它实现了一个特征。在这种情况下,fn get_ncollide_shape&lt;T: Shape&lt;P, M&gt;&gt;(&amp;self) -&gt; Box&lt;T&gt;; 没有意义,因为没有办法调用它。该函数有无数个版本,因为它在T 上是参数化的,因此无法使用该函数,因为它需要在运行时决定调用哪个版本,并且选项是已知的。
  • @loganfsmyth 我不确定你的真正意思。该部分仅返回实现 Shape 特征的任何对象。这部分代码可以编译,到目前为止看起来还不错。在错误消息中,它引用第 19 行(在结构定义中)作为问题。对这部分有什么想法吗?
  • 错误出现在第 19 行,因为这是您声明特征的盒装版本的地方。错误说“注意:方法get_ncollide_shape 具有泛型类型参数”,因为像get_ncollide_shape&lt;T: Shape&lt;P, M&gt;&gt; 这样的泛型参数在将被装箱的特征上是不允许的。 Rust 是一种编译语言,因此它需要能够创建代码来完成您所要求的工作,而它不能完成您所要求的工作。

标签: rust


【解决方案1】:

Rust 是一种编译语言,因此当它编译您的代码时,它需要知道生成机器代码可能需要的所有信息。

当你说

trait MyTrait {
  fn do_thing() -> Box<u32>;
}

struct Foo {
   field: Box<MyTrait>
}

你告诉 Rust Foo 将包含一个 box 包含任何实现 MyTrait 的东西。通过对类型进行装箱,编译器将删除该特征未涵盖的有关数据类型的任何附加数据。这些trait objects 被实现为一组数据字段和一个函数表(称为vtable),其中包含特征公开的函数,因此可以调用它们。

当你改变时

fn do_thing() -> Box<u32>;

fn do_thing<T>() -> Box<T>;

它可能看起来很相似,但行为却大不相同。我们举一个普通函数的例子

fn do_thing<T>(val: T) { }

fn main() {
  do_thing(true);
  do_thing(45 as u32);
}

编译器执行所谓的monomorphization,这意味着您在编译器中的代码本质上变成了

fn do_thing_bool(val: bool) { }
fn do_thing_num(val: u32) { }

fn main() {
  do_thing_bool(true);
  do_thing_num(45 as u32);
}

要意识到的关键是你要求它为你的特质做同样的事情。问题是编译器做不到。上面的示例依赖于提前知道 do_thing 在一种情况下是用数字调用的,而在另一种情况下是布尔值调用的,它可以 100% 确定地知道这是该函数的唯一两种使用方式。

使用您的代码

trait MyTrait {
  fn do_thing<T>() -> Box<T>;
}

编译器不知道do_thing 将被调用什么类型,因此它无法生成您需要调用的函数。为此,无论您将实现 Collidable 的结构转换为装箱对象,它都必须知道 get_ncollide_shape 可能具有的每种可能的返回类型,这是不受支持的。

其他链接:

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-12
    • 1970-01-01
    • 2021-02-01
    • 2023-03-06
    相关资源
    最近更新 更多