如何实现涉及特征对象的内置类型的通用交换std :: ops?

时间:2018-04-23 07:02:03

标签: generics rust operator-overloading traits

我有:

use std::ops::{Add, Div, Mul, Neg, Sub};

pub trait Hilbert:
    Add + Sub + Mul + Div + Neg + Mul<f64, Output = Self> + Div<f64, Output = Self> + Sized + Copy
{
    fn dot(&self, other: &Self) -> f64;
    fn magnitude(&self) -> f64;
}

fn g<T: Hilbert>(x: T) -> f64 {
    let a = (x * 2.0).dot(&x);
    let b = (2.0 * x).dot(&x);
    a + b
}
error[E0277]: cannot multiply `T` to `{float}`
  --> src/main.rs:12:18
   |
12 |     let b = (2.0 * x).dot(&x);
   |                  ^ no implementation for `{float} * T`
   |
   = help: the trait `std::ops::Mul<T>` is not implemented for `{float}`

我希望所有H * a s a * H Hilbert等于H。在另一个答案的静脉中,我会尝试:

impl<T: Hilbert> Mul<T> for f64 {
    type Output = T;

    fn mul(self, other: T) -> T {
        other * self
    }
}

但这会产生:

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g. `MyStruct<T>`); only traits defined in the current crate can be implemented for a type parameter
  --> src/main.rs:16:1
   |
16 | / impl<T: Hilbert> Mul<T> for f64 {
17 | |     type Output = T;
18 | |
19 | |     fn mul(self, other: T) -> T {
20 | |         other * self
21 | |     }
22 | | }
   | |_^

为什么不允许这样做?什么是为特征对象指定交换乘法的正确方法?

1 个答案:

答案 0 :(得分:3)

  

为什么不允许这样做?

Rust强制执行一项策略,即必须在与特征或类型相同的包中定义实现。你的箱子里都没有Mulf64

这可以防止关于将使用哪种实现的含糊不清。它使编译器可以轻松地强制每个类型最多存在一个特征的一个实例,因为它只需要检查那些包中的实现。如果任何其他包可以定义实例,那么编译器必须看到处。但是,试图推理代码的人类也必须熟悉每个箱子,以便猜测哪些实现最终会被使用。特征实现不是Rust中的命名项,因此您甚至无法明确它。 Here's some background

常见的解决方法是使用包装类型。这样做的运行成本为零,但它会使API更麻烦。

您还可以定义自己的数字特征,这意味着所有AddMul等,为所有基元类型实现 ,并将其用作绑定在Hilbert而不是所有个别特征。

但无论你走哪条路线,这都会很混乱。我会质疑使用相同的运算符对标量,非标量和混合的好处。只需在API中添加新方法即可

fn scale(self, by: f64) -> Self;

除了没有陷入所有这些特征界限和变通方法的复杂混乱之外,代码的意图更加清晰。您不必查看每个变量的类型,以区别于两个标量的乘法。

fn g<T: Hilbert>(x: T) -> f64 {
    let a = x.scale(2.0).dot(&x);
    let b = x.scale(2.0).dot(&x);
    a + b
}