我想实现向量与标量的频繁数学乘法: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这可能很有用。