在多个类型上实现 trait 的问题。

4
作为一项练习,我正在尝试创建一个温度单位库。
目前,以下代码无法编译,错误信息对我来说不清楚。问题似乎是类型冲突。
type Fahrenheit = f64;
type Celcius = f64;
type Kelvin = f64;


trait TemperatureUnit {
    fn to_kelvin(&self) -> Kelvin;
}

impl TemperatureUnit for Fahrenheit {
    fn to_kelvin(&self) -> Kelvin {
        (*self + 459.67) * 5/9
    }
}

impl TemperatureUnit for Celcius {
    fn to_kelvin(&self) -> Kelvin {
        *self + 273.15
    } 
}

impl TemperatureUnit for Kelvin {
    fn to_kelvin(&self) -> Kelvin {
        *self
    }
}

错误:

error[E0119]: conflicting implementations of trait `TemperatureUnit` for type `f64`
  --> src/lib.rs:18:1
   |
12 | impl TemperatureUnit for Fahrenheit {
   | ----------------------------------- first implementation here
...
18 | impl TemperatureUnit for Celcius {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `f64`

error[E0119]: conflicting implementations of trait `TemperatureUnit` for type `f64`
  --> src/lib.rs:24:1
   |
12 | impl TemperatureUnit for Fahrenheit {
   | ----------------------------------- first implementation here
...
24 | impl TemperatureUnit for Kelvin {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `f64`

Playground

我是 Rust 的新手,也许这不是实现编译器强制类型安全的单位转换的正确方法。

2个回答

6

当你使用 type 时,你实际上是在创建一个类型别名,而不是一个新的类型。这就像在 C++ 中使用 typedef

type Fahrenheit = f64;

这个字面上的意思是"当我说'华氏度'时,假装我说的是f64"。不是一个新类型,也不是一个独立的结构,字面上是相同的。所以你只需要为f64实现TemperatureUnit三次。

如果你想在你的控制下包装f64成为自定义类型,则可以考虑使用newtype模式

pub struct Fahrenheit(pub f64);

现在,Fahrenheit是一个独立的结构体,恰好与f64同构。你可以使用Fahrenheit(x)构造函数从f64转换为Fahrenheit,并使用my_fahrenheit.0投射函数进行反向转换。如果你愿意,你也可以给投射函数命名。

pub struct Fahrenheit { pub float_repr: f64 };

Rust几乎肯定会将其内部编译成实际的f64。只有一个字段的结构通常不会有任何开销。但是我们可以使用repr(transparent)来确保这一点。
#[repr(transparent)]
pub struct Fahrenheit(pub f64);

这将确保在运行时,Fahrenheitf64具有相同的表示(甚至可以在它们之间定义transmute),而在编译时它们是不同的类型。
唯一剩下的问题是决定您想要您的新类型实现哪些特性。默认情况下,它不会从f64继承任何内容,因此在声明之前,您可能需要包含一个庞大的derive(...)子句,其中包括EqOrdCloneCopy等(非详尽列表; 您比我更了解您的用例)。

1
除非你需要用于不安全的代码,否则不要使用#[repr(transparent)]:据我所知,今天它实际上是相同的,而且当不需要时,我期望默认表示更具性能。 - Chayim Friedman
感谢您的时间。您的回答清晰而完整。 不确定在计算机科学中“同构”一词的含义(transmute == inverse mapping?)。 - gberth
@gberth 抱歉,范畴论思维有点超前了。不要对此过于深究。我只是指这两者存储相同的数据。有一个映射 Fahrenheit -> f64 和一个 f64 -> Fahrenheit 的映射,它们互为反函数。 - Silvio Mayolo
谢谢你的回答。Rust 的词汇量非常广泛,所以我想知道是否有什么我漏掉了。:^) - gberth

1
问题在于 type 只是别名,而不是创建新类型。Rust 抱怨这三个 impl 块试图在同一类型上实现 TemperatureUnit 三次。
相反,您应该声明新类型。下面的示例使用 structRust playground
struct Fahrenheit(f64);
struct Celcius(f64);
struct Kelvin(f64);


trait TemperatureUnit {
    fn to_kelvin(&self) -> Kelvin;
}

impl TemperatureUnit for Fahrenheit {
    fn to_kelvin(&self) -> Kelvin {
        Kelvin((self.0 + 459.67) * 5.0/9.0)
    }
}

impl TemperatureUnit for Celcius {
    fn to_kelvin(&self) -> Kelvin {
        Kelvin(self.0 + 273.15)
    } 
}

impl TemperatureUnit for Kelvin {
    fn to_kelvin(&self) -> Kelvin {
        Kelvin(self.0)
    }
}


fn main()
{
    println!("Hey");
}

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