Rust 动态分配具有通用参数的特征
Rust dynamic dispatch on traits with generic parameters
我有以下
Rust Playground permalink
来自我的追随者 Ray Tracing in a Weekend。
在实施 material 时,我选择创建特征 Material
。
我的想法是,世界上的物体将具有 material
属性,并且在光线 Hit
上 Hit
可以查看该物体并请求 material 一经请求。这对我遵循类似想法的 Normal
特征来说效果很好。我用动态调度实现了这一切,尽管我认为我掌握了足够的知识,也可以用特征边界静态地完成它。
在链接的代码中,您会看到第 13 行,我要求那些实现 Normal
的方法有一个 return 和 Material
的方法。这表明现在 Normal
不再有资格成为特征对象 error[E0038]: the trait
Normalcannot be made into an object
如果我从 这样的问题中理解正确,那么似乎由于泛型是单态化的,Rust 运行时不可能泄露 material
的适当方法,因为表面上可能有一个不同的方法每种实现 Normal
的类型一个?这并不太符合我的要求,因为看起来,与 Rust 运行时放在相同的位置,我可以看看我现在手头的 Normal
实现者,比如 Sphere
并说我会查看 Sphere
的 vtable。你能解释一下我哪里错了吗?
从那里开始,我尝试简单地与编译器作斗争,然后转向静态调度。
第 17-21 行
struct Hit<'a> {
point: Vec3,
distance: f32,
object: &'a dyn Normal,
}
成为
struct Hit<'a, T: Normal> {
point: Vec3,
distance: f32,
object: &'a T,
}
从那里开始,我试图一个接一个地堵上一个洞,似乎看不到尽头。
除了了解我当前理解的根本错误之外,我还可以做出哪些不同的设计选择?
put in the same position as the Rust runtime, I would be able to look at the Normal implementer I have in hand at a moment, say Sphere and say I will look in Sphere's vtable
除非 Rust 运行时在这里无能为力。
事实上,Rust 没有 "something executing the code" 意义上的运行时。 Rust 运行时只执行设置和清理任务,但只要控制流在你的 main
函数内的某个地方,你就靠你自己了(在 no_std
环境中,即使这样也不存在).因此,每个动态调度都必须通过将 vtable 指针放在数据指针旁边来烘焙到类型中 - 请参阅 this documentation 了解更多详细信息。
但是,正如您已经指出的那样,由于泛型是单态的,因此 Normal
的每个实现都不会有一个 fn material
:会有一个未知的,可能是无限的这些功能的家族,每个类型一个实现 Material
。注意 "unknown, potentially infinite" 位:因为你不能泄漏隐私部分,如果 Normal
特征是 public,那么 Material
特征也必须是 public - 并且那么没有什么会阻止您的代码的用户添加另一个您的代码不知道的 Material
实现,它根本无法烘焙到 dyn Normal
.
的 vtable 中
这就是泛型方法不是对象安全的原因。它们无法放入 trait 对象 vtable,因为在创建 trait 对象时我们并不知道它们。
我可能遗漏了一些东西,但我认为你可以 - 至少从我所看到的 - 沿着你的道路走得更远。
我想你可以改变这个功能:
fn material<T: Material>(&self) -> T;
就目前而言,它表示:任何 Normal
提供一个函数 material
,其中 caller 可以指定一个 Material
该函数将 return.
但是(我认为)你想声明的是:任何 Normal
都有一个 material 可以由调用者请求。但调用者无权指示任何将被 return 编辑的 Material
。要将其转换为代码,您可以声明:
fn material(&self) -> &dyn Material;
这表明 material
return 是一个 Material
(作为特征对象)。
然后,Sphere
可以实现 Normal
:
impl<'a> Normal for Sphere<'a> {
fn normal(&self, point: &Vec3) -> Ray {
Ray::new(point, &(point - &self.center))
}
fn material(&self) -> &dyn Material {
self.material
}
}
我有以下 Rust Playground permalink
来自我的追随者 Ray Tracing in a Weekend。
在实施 material 时,我选择创建特征 Material
。
我的想法是,世界上的物体将具有 material
属性,并且在光线 Hit
上 Hit
可以查看该物体并请求 material 一经请求。这对我遵循类似想法的 Normal
特征来说效果很好。我用动态调度实现了这一切,尽管我认为我掌握了足够的知识,也可以用特征边界静态地完成它。
在链接的代码中,您会看到第 13 行,我要求那些实现 Normal
的方法有一个 return 和 Material
的方法。这表明现在 Normal
不再有资格成为特征对象 error[E0038]: the trait
Normalcannot be made into an object
如果我从 material
的适当方法,因为表面上可能有一个不同的方法每种实现 Normal
的类型一个?这并不太符合我的要求,因为看起来,与 Rust 运行时放在相同的位置,我可以看看我现在手头的 Normal
实现者,比如 Sphere
并说我会查看 Sphere
的 vtable。你能解释一下我哪里错了吗?
从那里开始,我尝试简单地与编译器作斗争,然后转向静态调度。 第 17-21 行
struct Hit<'a> {
point: Vec3,
distance: f32,
object: &'a dyn Normal,
}
成为
struct Hit<'a, T: Normal> {
point: Vec3,
distance: f32,
object: &'a T,
}
从那里开始,我试图一个接一个地堵上一个洞,似乎看不到尽头。
除了了解我当前理解的根本错误之外,我还可以做出哪些不同的设计选择?
put in the same position as the Rust runtime, I would be able to look at the Normal implementer I have in hand at a moment, say Sphere and say I will look in Sphere's vtable
除非 Rust 运行时在这里无能为力。
事实上,Rust 没有 "something executing the code" 意义上的运行时。 Rust 运行时只执行设置和清理任务,但只要控制流在你的 main
函数内的某个地方,你就靠你自己了(在 no_std
环境中,即使这样也不存在).因此,每个动态调度都必须通过将 vtable 指针放在数据指针旁边来烘焙到类型中 - 请参阅 this documentation 了解更多详细信息。
但是,正如您已经指出的那样,由于泛型是单态的,因此 Normal
的每个实现都不会有一个 fn material
:会有一个未知的,可能是无限的这些功能的家族,每个类型一个实现 Material
。注意 "unknown, potentially infinite" 位:因为你不能泄漏隐私部分,如果 Normal
特征是 public,那么 Material
特征也必须是 public - 并且那么没有什么会阻止您的代码的用户添加另一个您的代码不知道的 Material
实现,它根本无法烘焙到 dyn Normal
.
这就是泛型方法不是对象安全的原因。它们无法放入 trait 对象 vtable,因为在创建 trait 对象时我们并不知道它们。
我可能遗漏了一些东西,但我认为你可以 - 至少从我所看到的 - 沿着你的道路走得更远。
我想你可以改变这个功能:
fn material<T: Material>(&self) -> T;
就目前而言,它表示:任何 Normal
提供一个函数 material
,其中 caller 可以指定一个 Material
该函数将 return.
但是(我认为)你想声明的是:任何 Normal
都有一个 material 可以由调用者请求。但调用者无权指示任何将被 return 编辑的 Material
。要将其转换为代码,您可以声明:
fn material(&self) -> &dyn Material;
这表明 material
return 是一个 Material
(作为特征对象)。
然后,Sphere
可以实现 Normal
:
impl<'a> Normal for Sphere<'a> {
fn normal(&self, point: &Vec3) -> Ray {
Ray::new(point, &(point - &self.center))
}
fn material(&self) -> &dyn Material {
self.material
}
}