【问题标题】:Casting to a generic type转换为泛型类型
【发布时间】:2015-06-22 16:20:56
【问题描述】:

我有一个关于 Rust(1.0 版)中泛型的新手问题。

假设我编写了一个通用函数来进行除法。别介意这种功能的用处;这是一个让这个问题保持简单的简单功能。

fn divide<T: std::ops::Div>(a: T, b: T) -> T {
    a / b
}

fn main() {
    println!("{}", divide(42, 18))
}

这个程序编译失败。

src/main.rs:2:5: 2:10 error: mismatched types:
 expected `T`,
    found `<T as core::ops::Div>::Output`
(expected type parameter,
    found associated type) [E0308]
src/main.rs:2     a / b
                  ^~~~~

我了解编译器错误告诉我除法运算的结果是类型Output,而不是T,我在standard library documentation 中看到Output 类型。

如何从Output 转换为T?我尝试使用as 进行投射。

fn divide<T: std::ops::Div>(a: T, b: T) -> T {
    (a / b) as T
}

fn main() {
    println!("{}", divide(42, 18))
}

这会导致不同的编译器错误。

src/main.rs:2:5: 2:17 error: non-scalar cast: `<T as core::ops::Div>::Output` as `T`
src/main.rs:2     (a / b) as T
                  ^~~~~~~~~~~~

我没有使这项工作发挥作用的想法,并且我意识到我对这里的语言的基本知识缺乏了解,但我什至不知道要寻找什么来使这项工作发挥作用。帮忙?

【问题讨论】:

标签: rust


【解决方案1】:

您只需将T::Output 指定为函数的返回类型:

fn divide<T: std::ops::Div>(a: T, b: T) -> T::Output {
    a / b
}

编辑以添加更多关于为什么不能在函数内进行强制转换的解释

当你在你的泛型函数中时,编译器还不知道你可以将T 转换为T::Output,所以这个转换是无效的。它们是泛型类型,可以是任何东西,编译器怎么知道你可以从T 转换为T::Output

a / b 产生 T::Output 类型的东西,所以在上面的解决方案中没有强制转换,T::Output 是正确的类型。

使用 std::convert::From 编辑以添加另一个可能的解决方案

(我认为)最通用的实现是当您知道从T::Output 到 T 的转换是可能的。您可以绑定T 以实现FromT::Output。 这是一个完整的例子:

use std::ops::Div;
use std::convert::From;

fn divide<T: Div>(a: T, b: T) -> T
    where T: From<<T as Div>::Output>
{
    T::from(a / b)
}

#[derive(Debug)]
struct Bip(u32);

impl Div for Bip {
    type Output = f32;

    fn div(self, rhs: Bip) -> f32 {
        (self.0 / rhs.0) as f32
    }
}

impl From<f32> for Bip {
    fn from(value: f32) -> Self {
        Bip(value as u32)
    }
}

fn main() {
    println!("{:?}", divide(12, 4));
    println!("{:?}", divide(Bip(12), Bip(4)));
}

【讨论】:

  • 行得通,但为什么行得通?如果我用u32 类型的T 调用divide 并将返回值分配给u32 类型的变量(即let x: u32 = divide(42u32, 18u32);),那么它会编译。为什么divide -&gt; T里面的return表达式不能从T::Output转换成T,但是在divide外面使用返回值时,类型系统允许转换(从u32::Outputu32)?即,将::Output 附加到divide 的返回类型会将转换从函数的内部移动到函数的外部。这是怎么回事?
  • 因为当您在泛型函数divide 中时,编译器还不知道您可以将T 转换为T:Output。 (它们是通用的,它们可以是任何东西,甚至是不能转换的东西)a / b 产生 T::Output 类型的东西,所以在上面的解决方案中没有转换,T::Output 只是正确的类型。
【解决方案2】:

Andreas 的回答解释了为什么强制转换是不可能的,并且使函数尽可能通用可以解决这个问题。

然而,这不是唯一的解决方案。 Rust 还支持约束泛型函数的关联类型(此处为Output)。

另一种选择是:

use std::ops::Div;

fn divide<T>(a: T, b: T) -> T
    where T: Div<Output = T>
{
    a / b
}

&lt;Output = T&gt; 位指示编译器仅在此函数中接受 T 类型,Div 的实现具有等于 TOutput。这显然更具限制性,但它允许确保divide 的结果是T 类型。

【讨论】:

  • = 约束的要点。我在官方文档中没有找到太多,你能给我们一些参考吗?但是,要添加更多内容,有时您可以要求为T 实现From 特征。 (我再次编辑了我的回复)
  • @AndreaP:在Associated Types 章节的最后提到了这一点。关于它们没什么好说的,所以文档也没有说太多,但我承认很容易错过。
猜你喜欢
  • 1970-01-01
  • 2015-11-17
  • 1970-01-01
  • 2020-06-16
  • 1970-01-01
  • 1970-01-01
  • 2012-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多