【问题标题】:How do I require a generic type implement an operation like Add, Sub, Mul, or Div in a generic function?如何要求泛型类型在泛型函数中实现 Add、Sub、Mul 或 Div 之类的操作?
【发布时间】:2015-03-21 15:18:36
【问题描述】:

我正在尝试在 Rust 中实现一个通用函数,其中对参数的唯一要求是应该定义乘法运算。我正在尝试实现一个通用的“电源”,但将使用更简单的cube 函数来说明问题:

use std::ops::Mul;

fn cube<T: Mul>(x: T) -> T {
    x * x * x
}

fn main() {
    println!("5^3 = {}", cube(5));
}

编译时出现此错误:

error[E0369]: binary operation `*` cannot be applied to type `<T as std::ops::Mul>::Output`
 --> src/main.rs:4:5
  |
4 |     x * x * x
  |     ^^^^^^^^^
  |
  = note: an implementation of `std::ops::Mul` might be missing for `<T as std::ops::Mul>::Output`

这是什么意思?我选择了错误的特质吗?我该如何解决这个问题?

【问题讨论】:

    标签: generics rust


    【解决方案1】:

    让我们稍微分解一下你的例子:

    fn cube<T: Mul>(x: T) -> T {
        let a = x * x;
        let b = a * x;
        b
    }
    

    ab 的类型是什么?在这种情况下,a 的类型是 &lt;T as std::ops::Mul&gt;::Output — 从错误消息中听起来很熟悉?然后,我们再次尝试将该类型乘以 x,但不能保证 Output 可以乘以任何值!

    让我们做最简单的事情,说T * T需要导致T

    fn cube<T: Mul<Output = T>>(x: T) -> T {
        x * x * x
    }
    

    不幸的是,这会产生两个类似的错误:

    error[E0382]: use of moved value: `x`
     --> src/lib.rs:6:9
      |
    6 |     x * x * x
      |     -   ^ value used here after move
      |     |
      |     value moved here
      |
      = note: move occurs because `x` has type `T`, which does not implement the `Copy` trait
    

    这是因为Mul trait takes arguments by value,所以我们添加了Copy,这样我们就可以复制这些值。

    我也改用where 子句,因为我更喜欢它,而且内联这么多也很笨拙:

    fn cube<T>(x: T) -> T
    where
        T: Mul<Output = T> + Copy
    {
        x * x * x
    }
    

    另见:

    【讨论】:

      【解决方案2】:

      绑定T: Mul 并不意味着二元运算符的结果也是T 类型。结果类型是该特征的关联类型Output

      另一个问题是在 Rust 1.0 之前,操作符特征从传递引用切换到传递值。在通用代码中,这可能有点让人头疼(至少现在是这样),因为这些运算符使用它们的操作数,除非您还要求类型为Copy

      为了完整起见(如果您不想要求Copy),让我添加一些关于可能的替代方向的信息。

      为了通用代码,鼓励“数字类型”的作者提供这些运算符特征的额外非消耗实现,这样您就不需要CopyClone .例如,标准库已经提供了以下实现:

       f64 implements Mul< f64>
       f64 implements Mul<&f64>
      &f64 implements Mul< f64>
      &f64 implements Mul<&f64>
      

      每个实现都有f64 作为Output 类型。直接利用这些特征并不漂亮:

      fn cube<T>(x: &T) -> T
      where
          for<'a> T: Mul<&'a T, Output = T>,
          for<'a, 'b> &'a T: Mul<&'b T, Output = T>,
      {
          x * x * x
      }
      

      最终,我们可能会获得一些(稍微)更高级别的特征,这将减少噪音。例如:T: Mul2 可以暗示 T: Mul&lt;T&gt; + Mul&lt;&amp;T&gt;&amp;T: Mul&lt;T&gt; + Mul&lt;&amp;T&gt;,但在撰写本文时,Rust 编译器似乎无法处理这个问题。至少我无法成功编译以下代码:

      use std::ops::Mul;
      
      pub trait Mul2
      where
          Self: Mul<Self, Output = Self>,
          Self: for<'a> Mul<&'a Self, Output = Self>,
          for<'a> &'a Self: Mul<Self, Output = Self>,
          for<'a, 'b> &'a Self: Mul<&'b Self, Output = Self>,
      {
      }
      
      impl<T> Mul2 for T
      where
          T: Mul<T, Output = T>,
          T: for<'a> Mul<&'a T, Output = T>,
          for<'a> &'a T: Mul<T, Output = T>,
          for<'a, 'b> &'a T: Mul<&'b T, Output = T>,
      {
      }
      
      fn cube<T: Mul2>(x: &T) -> T {
          x * x * x
      }
      
      fn main() {
          let c = cube(&2.3);
          println!("Hello, world! {}", c)
      }
      

      我认为可以肯定地说这方面的情况会有所改善。目前,在 Rust 中通用实现数值算法的能力并没有我希望的那么好。

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-03-28
      • 2019-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多