我应该借用还是复制我的小数据类型?

6
struct Vec {
    data: [f32; 3],
}

impl Vec {
    fn dot(&self, other: &Vec) -> f32 {
        ..
    }
    // vs
    fn dot(self, other: Vec) -> f32 {
        ..
    }
}

我目前正在编写一个向量数学库,我想知道是应该借用还是复制我的向量类型。
目前,我为Vec实现了Copy,这使得API更加友好,因为你不必一直写&。
但它需要更复杂的约束,因为现在所有的约束都需要满足Copy。
哪一个潜在地产生更好的性能?为什么?
哪一个潜在地产生更好的人机交互体验?为什么?
编辑:
我创建了一个小型的microbenchmark
test bref_f32 ... bench:   2,736,055 ns/iter (+/- 364,885)
test bref_f64 ... bench:   4,872,076 ns/iter (+/- 436,928)
test copy_f32 ... bench:   2,708,568 ns/iter (+/- 31,162)
test copy_f64 ... bench:   4,890,014 ns/iter (+/- 553,050)

就性能而言,对于这个例子,refcopy之间似乎没有区别。

对于库用户来说,copy似乎提供了更好的人机工程学。


2
你真的确定想询问“惯用语”而不是“性能”或“简化边界”吗?因为惯用语非常主观,我担心两种替代方案都可能被推荐,而且没有明确的客观答案...在这种情况下,你的问题很可能会被关闭(主要基于观点)。 - Matthieu M.
@MatthieuM。我认为有一些指南可以告诉我们什么是符合Rust语言习惯的代码,所以我重新表述了问题并添加了一个例子。 - Maik Klein
2个回答

9
Rust不是一个仅有高妙美学目标和单一用途的纯学术语言。Rust是一种系统编程语言,这意味着它是实用主义的。
通常的经验法则是您的接口应正确地记录所有者:
当您放弃所有权时,请按值传递。
在您暂时放弃所有权时,请通过引用(可能是可变的)传递。
然而,Copy trait 是实用主义发挥的完美例子。它认识到按引用传递可能会很麻烦(有谁想要输入 (&1 + &x) * &y?),因此为那些不需要是仿射的类型(即,在销毁时没有特殊操作的类型)创建了一个逃生口。
结果,如果您的类型可以保证为 Copy,并且保持为 Copy,则将其标记为 Copy 可以改善用户使用该类型的方式,从而提高人体工效学。我会鼓励您这样做,但请注意,稍后删除 Copy trait 将是一种向后不兼容的更改。
一旦类型被标记为 Copy,我就不会犹豫利用这个事实并按值传递它。毕竟,如果该类型从未按值传递,那么一开始将其标记为 Copy 也就没有意义了。
唯一的限制是性能原因。
Copy 不会强制复制,它只是允许复制而已。这意味着优化器可以自由使用复制…或者不使用。
对于小型类型来说,无论发生什么,性能都不太可能有太大的差异;对于较大的类型,如果性能很重要,我会鼓励您对各种接口进行基准测试。在 64 位架构上,Vec 的大小仅为指针/引用的 1.5 倍,因此它实际上处于灰色地带。有时复制会更慢(较大的复制),但有时拥有本地副本的好处将启用不会触发指针的优化。
然而,这种基准测试充满风险,特别是因为它非常依赖于该函数是否被内联以及更多或更少的删除复制的余地。

5

在这种情况下,我建议借用,因为拥有不是问题。所以,我猜你的代码会像这样:

struct Vec {
    data: [f32; 3],
}

impl Vec {
    fn dot(&self, other: &Vec) -> f32 {..}
}

2
你能解释一下为什么你会推荐那个吗? - Maik Klein
我会推荐这样做,因为复制向量时会使用更多的内存来创建一个全新的向量。如果不需要创建新的向量,就不要这样做。 - Eli Sadoff
3
当你复制一个向量时会发生的情况取决于优化编译器的心血来潮,如果编译器认为可以简化,则可能会对其进行修改。 - Shepmaster

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