是否可以在外国类型上实现特征?

Per*_*ker 5 traits rust

我想实现向量与标量的频繁数学乘法:k * v = (k * v0, k * v1, k* v2..)。代码如下:

#[derive(Debug)]
struct V(Vec<i32>);

impl std::ops::Mul<V> for i32 {
    type Output = V;
    fn mul(self, v: V) -> V {
        V(v.0.iter().map(|v| v * self).collect())
    }
}

fn main() {
    let v = V(vec![1,2]);
    println!("{:?}", 3 * v);
}
Run Code Online (Sandbox Code Playgroud)

这个解决方案有效,但是我想避免结构 V 的定义,而是编写类似的代码:

impl std::ops::Mul<Vec<i32>> for i32 {
    type Output = Vec<i32>;
    fn mul(self, v: Vec<i32>) -> Vec<i32> {
        v.iter().map(|v| v * self).collect()
    }
}
Run Code Online (Sandbox Code Playgroud)

这会引发以下错误:

 only traits defined in the current crate can be implemented for arbitrary types
  --> src/main.rs:45:1
   |
45 | impl std::ops::Mul<Vec<i32>> for i32 {
   | ^^^^^-----------------------^^^^^---
   | |    |                           |
   | |    |                           `i32` is not defined in the current crate
   | |    `Vec` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead
Run Code Online (Sandbox Code Playgroud)

是否可以对外国类型使用乘法特征?

cam*_*024 10

简而言之,不。

你遇到了“孤儿规则”。基本思想是,如果您想X在 struct/enum/union 上实现 Trait Y,则必须在当前包中定义至少其中之一。

这有时有些限制,但它是 Rust“一致性规则”的产物。一般来说,特征和类型的每种组合最多只能有 1 个实现。所以下面的代码是无效的:

struct Wrapper<T>(T);

trait Print {
  fn print(&self);
}

impl Print for Wrapper<i32> {
  fn print(&self) {
    println!("a very special int: {}", self);
  }
}

impl<T: Display> Print for Wrapper<T> {
  fn print(&self) {
    print!("a normal type: {}", self);
  }
}
Run Code Online (Sandbox Code Playgroud)

想象一下您编写的代码0i32.print()。rustc 选择哪种实现?答案尚不清楚(在前专业化的世界中),因此考虑到 Rust 的显式原则,该代码被彻底拒绝。

孤儿规则是一种通常使生态系统更可用的机制。打破孤儿规则本质上并没有什么不合理的(如果可能的话),但是,它会导致一个不幸的场景,您可能会使用 2 个板条箱来为某些外来类型和特征定义自己的实现。

例如,想象一下,一个板条箱决定添加这个(看起来相当合理的)实现:

impl<T> Display for Vec<T> where T: Display {
  fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
    todo!()
  }
}
Run Code Online (Sandbox Code Playgroud)

这很好,直到您想要导入另一个也定义了类似的 impl 的 crate。即使代码相同,它们也将是单独的实现,因此会不一致并且无法编译。

孤儿规则可以保护生态系统免受此类问题的影响。

包装器在 Rust 中是惯用的,并且不会产生运行时成本。如果您发现自己写了很多东西,我已经编写了一个库来自动化许多调用的样板文件,microtype这可能很有用。