为trait对象和trait直接实现者实现trait的实现方法

15

我有一个结构体,主要封装了一个向量:

struct Group<S> {
    elements: Vec<S>
}

我还有一个简单的特性,也被实现在其他结构体中:

trait Solid {
    fn intersect(&self, ray: f32) -> f32;
}

我想要为Group实现Solid,但我希望能够同时用于相同实现的Solid列表和混合实现的Solid列表。基本上我想要同时使用Group<Box<Solid>>Group<Sphere>(其中Sphere实现了Solid)。

目前我正在使用类似这样的代码:

impl Solid for Group<Box<Solid>> {
    fn intersect(&self, ray: f32) -> f32 {
        //do stuff
    }
}

impl<S: Solid> Solid for Group<S> {
    fn intersect(&self, ray: f32) -> f32 {
        //do the same stuff, code copy-pasted from previous impl
    }
}

这样做是有效的,但是两行代码完全相同并不是惯用的解决方案。我一定漏掉了什么显而易见的东西吗?

在我的情况下,我发现两种trait实现之间存在明显的性能差异,因此总是使用Group<Box<Solid>>不是一个很好的选择。

1个回答

20

为所有实现了您的trait的SBox<S>实现您的trait。然后,您可以委托给现有的实现:

impl<S: Solid + ?Sized> Solid for Box<S> {
    fn intersect(&self, ray: f32) -> f32 {
        (**self).intersect(ray)
        // Some people prefer this less-ambiguous form
        // S::intersect(self, ray)
    }
}

你也会发现,对于参考资料做同样的处理也是很有用的:

impl<S: Solid + ?Sized> Solid for &'_ S {
    fn intersect(&self, ray: f32) -> f32 {
        (**self).intersect(ray)
        // Some people prefer this less-ambiguous form
        // S::intersect(self, ray)
    }
}

全部在一起:

trait Solid {
    fn intersect(&self, ray: f32) -> f32;
}

impl<S: Solid + ?Sized> Solid for Box<S> {
    fn intersect(&self, ray: f32) -> f32 {
        (**self).intersect(ray)
        // S::intersect(self, ray)
    }
}

impl<S: Solid + ?Sized> Solid for &'_ S {
    fn intersect(&self, ray: f32) -> f32 {
        (**self).intersect(ray)
        // S::intersect(self, ray)
    }
}

struct Group<S>(Vec<S>);

impl<S: Solid> Solid for Group<S> {
    fn intersect(&self, _ray: f32) -> f32 {
        42.42
    }
}

struct Point;

impl Solid for Point {
    fn intersect(&self, _ray: f32) -> f32 {
        100.
    }
}

fn main() {
    let direct = Group(vec![Point]);
    let boxed = Group(vec![Box::new(Point)]);
    let pt = Point;
    let reference = Group(vec![&pt]);

    let mixed: Group<Box<dyn Solid>> = Group(vec![
        Box::new(direct),
        Box::new(boxed),
        Box::new(Point),
        Box::new(reference),
    ]);

    mixed.intersect(1.0);
}

?Sized约束允许S在编译时不需要已知大小。重要的是,这使您可以传入“trait对象”,例如Box<dyn Solid>&dyn Solid作为类型Solid没有已知大小。

另请参阅:


1
@CarlLevasseur 我已经更新了关于?Sized的现有句子,是否更清晰了? - Shepmaster
是的,所以如果 Box<Solid> 是一个 Solid,那么在使用它作为一个需要 &Solid 的函数参数时,我们将有两个“重定向”,这对性能有影响吗?此外,在什么情况下实现引用会很有用? - Carl Levasseur
@CarlLevasseur 这会对性能产生影响吗 — 这取决于很多因素(调用频率、编译器优化、单态化等)。唯一的答案是进行性能分析以确保。何时实现引用是有用的 — 每当您拥有一个特质对象引用而不是一个装箱特质对象时(示例)...我认为我没有完全理解问题。 - Shepmaster
为什么'(**self).intersect(ray)'这部分能够编译通过?我猜测'**self'是'?Sized',可能不能在栈上存活。 - Garlic Xu
1
@GarlicXu 不过它不需要存在于堆栈上。当方法调用时,编译器会自动插入一个引用:Rust的确切自动解引用规则是什么? - Shepmaster
显示剩余2条评论

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