简体   繁体   中英

Problem implementing a trait to multiple types

As a practice exercise, I'm trying to create a temperature unit library.

Currently, the following code won't compile and the error message is unclear to me. The problem seems to be a type conflict.

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
    }
}

Errors:

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

I'm new to Rust, so maybe this is not the proper methodology to achieve a compiler-enforced type-safe unit conversion.

When you write type , you're making a type alias, not a new type. It's like when you use typedef in C++, for instance.

type Fahrenheit = f64;

This literally says "when I say 'Fahrenheit', pretend I said f64 instead". Not a new type, not a distinct structure, literally the same. So you just implemented TemperatureUnit for f64 three times.

If you want to wrap f64 in a custom type under your control, you're looking for the newtype pattern .

pub struct Fahrenheit(pub f64);

Now Fahrenheit is a distinct structure that happens to be isomorphic to f64 . You can go from f64 to Fahrenheit with the Fahrenheit(x) constructor, and you can go back with the my_fahrenheit.0 projection. You can also name the projection if you prefer.

pub struct Fahrenheit { pub float_repr: f64 };

Rust will almost certainly compile this internally to an actual f64 . Structs with only one field will generally have no overhead. But we can actually guarantee this with repr(transparent)

#[repr(transparent)]
pub struct Fahrenheit(pub f64);

This guarantees that, at runtime, Fahrenheit and f64 have the same representation (even to the extent that transmute is defined between them), while at compile-time they're distinct types.

The only thing left to decide is what traits you want your new type to implement. It will inherit nothing from f64 by default, so you'll likely have a massive derive(...) clause right before the declaration including Eq , Ord , Clone , Copy , etc. (non-exhaustive list; you know your use case better than I do)

The problem here is that type just aliases a type, it doesn't create a new one. Rust is complaining because those three impl blocks are trying to implement TemperatureUnit on the same type three times.

Instead, you should declare new types. The example below uses struct .

Rust 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");
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM