如何在通用函数中要求通用类型实现Add、Sub、Mul或Div等操作?

34

我正在尝试在Rust中实现一个通用函数,其参数的唯一要求是定义了乘法操作。我尝试实现一个通用的“幂”函数,但为了说明问题,我将使用一个更简单的立方函数:

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`

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

41

让我们来仔细看一下您的例子:

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

什么是 ab 的类型?在这种情况下,a 的类型是 <T as std::ops::Mul>::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 通过值传递参数,因此我们添加了 Copy,以便我们可以复制这些值。
我还切换到了 where 子句,因为我更喜欢它,而且在行内使用时会很笨重:
fn cube<T>(x: T) -> T
where
    T: Mul<Output = T> + Copy
{
    x * x * x
}

参见:


19
绑定的 T: Mul 并不意味着二元运算符的结果也是类型 T。结果类型是此 trait 的一个关联类型:Output
另一个问题是,在 Rust 1.0 之前,操作符 trait 从按引用传递切换到按值传递。在通用代码中,这可能有点麻烦(至少现在是这样),因为这些操作符会消耗它们的操作数,除非你还要求这些类型是 Copy
只是为了完整性(以防你不想要求 Copy),让我添加一些关于可能的替代方向的信息。
为了通用代码的缘故,“数字类型”的作者被鼓励提供这些操作符 trait 的额外的、非消耗性的实现,这样你就不需要 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<T> + Mul<&T>&T:Mul<T> + Mul<&T>,但在撰写本文时,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语言通用实现数值算法的能力还不如我所期望的那样好。


你能否解释一下(或者给个链接)“where for”符号,我之前没见过。 - Maxim Sloyko
where子句是一种更通用的约束泛型参数的方式。它们是通过https://github.com/rust-lang/rfcs/blob/master/text/0135-where.md引入的。 - sellibitze
for<'a> 符号是在 RFC 387 中引入的:https://github.com/rust-lang/rfcs/blob/master/text/0387-higher-ranked-trait-bounds.md。 - Chris Emerson
据我所知,这个遗憾的功能仍未实现。 - Uncle Dino

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