为什么Rust的Add trait需要获取所有权?

4

我正在编写一个玩具BigInt类,并在实现Add trait时注意到它会占用两个输入值的所有权。与这个方法借用引用相比,这种处理方式试图解决什么问题?由于我使用Vec作为底层数据结构,无法实现Copy,这使得使用起来稍微有些麻烦。

它带来的一个(小)不便之一的示例:

let i = BigInt::new();
//  - move occurs because `i` has type `BigInt`, which does not implement the `Copy` trait
let j = i + 16u32;
//      --------- `i` moved due to usage in operator
let k = i + 32u32;
//      ^ value used here after move

虽然我知道可以使用 .clone(),但我很好奇这种方式如何帮助程序员。

1个回答

3
为什么拥有能力很重要举个例子,因为String + &str可以在现有字符串上进行附加,而不必先进行克隆。您始终可以为引用实现Add.clone
struct A;

impl std::ops::Add for A {
    // normal impl
}

impl<'a> std::ops::Add for &'a A {
    // clone and delegate
}


fn main() {
    A + A; // This still compiles
    &A + &A; // This now compiles
    &A + A; A + &A; // These won't compile, but you can add those impls too, if you'd like

}

然而,你应该仅限于对于Copy类型进行这样的操作,以避免用户被隐藏的分配所惊讶。

对于Copy类型,您通常需要四种实现才能将两个值都添加到参考和将参考添加到值中,如下所示:

impl std::ops::Add<T> for T;
impl<'a> std::ops::Add<T> for &'a T;
impl<'a> std::ops::Add<&'a T> for T;
impl<'a, 'b> std::ops::Add<&'a T> for &'b T; // Not sure if two lifetimes are needed here, but shouldn't hurt

3
值得注意的是,标准库的 trait 实现者们 https://doc.rust-lang.org/std/ops/trait.Add.html#implementors 会为 i32 实现 4 次,每次对应一种引用的组合。 - N. Shead
我认为标准库没有为非Copy类型的引用实现它,以避免隐藏分配。因此,我认为对于Copy类型,值得做所有4个实现,我会将其添加到答案中。 - Filipe Rodrigues
这很有道理,我个人并不在意某些东西在内部进行堆分配,但我明白为什么在某些地方这是不可取的。而且我甚至没有考虑过能够为其引用实现特性。感谢您的解释! - Jeremy Meadows
你可以在引用类型中实现Add和.clone操作。有人能解释一下为什么这里提到了.clone操作,以及为什么在实现引用的Add时需要进行克隆操作吗? - anon2328
1
对于非Copy类型,当实现Add<&T> for &T时,您有两个&T可用。但是,如果您想将实现委托给Add<T> for T(或Add<&T> for T),则需要克隆&T以获取T,以便能够调用其他实现。 - Filipe Rodrigues

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