非标量转换:在进行泛型乘法时将“T”作为“f64”

Non-scalar cast: `T` as `f64` when doing generic multiplication

我想为泛型结构实现一个特征。特征块内的方法必须 return 非泛型类型。我在尝试投射时遇到问题:

struct Rectangle<T> {
    x: T,
    y: T,
    width: T,
    height: T,
}

trait Area {
    fn area(&self) -> f64;
}

impl<T> Area for Rectangle<T>
    where T: std::ops::Mul<Output=T> 
{
    fn area(&self) -> f64 {
        let t_area = self.height * self.width;
        let f_area = t_area as f64;
        f_area
    }
}

fn main() {
    let sq = Rectangle { x: 0, y: 0, height: 1, width: 1 };
    println!("{}", sq.area());
}

编译器输出为:

error: non-scalar cast: `T` as `f64`
  --> src/main.rs:22:22
   |
22 |         let f_area = t_area as f64;
   |                      ^^^^^^^^^^^^^

我可以在不使用不安全块的情况下将 T 转换为 f64 吗?

Can I, not using unsafe blocks, cast T to f64?

不, 这有很多充分的理由。 T可以是任何类型,所以它根本不需要与f64兼容或相似。您对 T 的唯一了解是,它实现了 std::ops::Mul<Output=T>。但这无济于事,因为该特征也没有说明 f64

所以您可以 做的是将 T 绑定为 std::ops::Mul<Output=f64>。这意味着将两个 T 相乘将得到 f64。然后你的函数就可以工作了(即使没有 as f64 转换),但是你使你的 impl 方式不那么通用。


解决这个问题的正确方法,是对多种数进行抽象。值得庆幸的是,num-traits crate has already done that. In particular, you are probably interested in the ToPrimitive trait。你的 impl 应该看起来有点像:

use num_traits::ToPrimitive;

impl<T> Area for Rectangle<T>
    where T: std::ops::Mul
          <T as std::ops::Mul>::Output: ToPrimitive

{
    fn area(&self) -> f64 {
        let t_area = self.height * self.width;
        t_area.to_f64().unwrap()
    }
}

小问题:我们这里有这个 unwrap(),当乘法结果无法转换为 f64 时会出现恐慌。我不太确定为什么会发生这种情况,但我们需要意识到这一点。也许没有这样的 unwrap().

会有更好的解决方案

当然你也可以创建自己的特质。类似于:

trait AsF64 {
    fn cast(self) -> f64;
}

impl AsF64 for i32 {
    fn cast(self) -> f64 { self as f64 }
}

// more impls for u32, u16, i16, ...

然后为 <T as std::ops::Mul>::Output: AsF64 实现。

Can I, not using unsafe blocks, cast T to f64?

什么是 T?将u8转换为f64的代码肯定与将u64转换为f64的代码不同(毕竟后者可能会失败)。

在我看来,你有两条路要走:

  • 使 Area 通用于 T,因此 return 成为 T
  • T 限制为可以转换为 f64
  • 的类型

我将演示后者:

impl<T> Area for Rectangle<T>
    where T: std::ops::Mul<Output=T> + Clone + std::convert::Into<f64>
{
    fn area(&self) -> f64 {
        self.height.clone().into() * self.width.clone().into()
    }
}