乘以常数的通用函数

Dan*_*erg 2 generics types rust

我正在尝试理解通用函数。我知道这个话题已经被广泛讨论,但我似乎无法理解一些细节。

我正在尝试类似的事情但更简单。我只想要一个double将参数乘以二的函数,并且我希望它适用于任何浮点或整数类型。到目前为止我有这个:

use std::convert::From;
use std::ops::Mul;

fn double<A>(x: A) -> A
where A: Mul<Output = A> + From<f32> {
    return x * A::from(2f32);
}
Run Code Online (Sandbox Code Playgroud)

当我用 float (f32f64) 调用它时,效果很好:

println!("{:?}", double(2f64));
Run Code Online (Sandbox Code Playgroud)

这给了我4.0

但它对于任何整数类型都会失败,例如:

println!("{:?}", double(2i32));
Run Code Online (Sandbox Code Playgroud)

这会产生以下错误:

println!("{:?}", double(2i32));
                 ------ ^^^^ the trait `From<f32>` is not implemented for `i32`
Run Code Online (Sandbox Code Playgroud)

我理解这个错误。没有通用的方法将浮点数转换为整数。如果我想2.5在我的函数内部而不是2.0.

我知道有这个TryFrom特性,但我不知道如何在这种情况下利用它。


我尝试了另一种方式,即需要A实现From<u32>,但这只是将错误从整数发生切换到浮点数发生。但令我惊讶的是,当我使用像 之类的有符号整数调用函数时,它也会导致错误2i32

我缺少什么?如何实现double任何数字类型的通用函数?

use*_*342 5

由于您只需将数字转换2为任何类型,因此您可以从尽可能小的类型进行转换,即From<u8>

use std::ops::Mul;

fn double<A>(x: A) -> A
where
    A: Mul<Output = A> + From<u8>,
{
    return x * A::from(2);
}

fn main() {
    println!("{}", double(1));
    println!("{}", double(1f32));
    println!("{}", double(1f64));
}
Run Code Online (Sandbox Code Playgroud)

操场

这种简单方法的唯一缺点是double(1i8)无法编译(但它可以与 、 等一起使用i16i32From<u8>未实现 fori8因为有些u8值无法表示为i8。然而,我们知道我们确实不需要将所有u8值表示为A,而只需表示 2u8 即可。这就是为什么我们要使用TryFrom

fn double<A>(x: A) -> A
where
    A: Mul<Output = A> + TryFrom<u8>,
{
    x * A::try_from(2).unwrap_or_else(|_| unreachable!())
}
Run Code Online (Sandbox Code Playgroud)

对于数字类型,展开永远不会出现恐慌,因为没有任何数字类型不能表示数字 2。

在这种情况下,您无需担心展开会使代码变慢,因为 rustc/LLVM 知道 egf64::try_from(2u8)是万无一失的,并省略了恐慌,如 godbolt 上所示。唯一的缺点是人类读者可能会感到困惑,这就是为什么使用unreachable!().