Conflicting implementations of traits with different constraints

Hi,

I need to read attributes and cast them to types I choose. It should be OK to cast a float to an integer and vice versa. It worked so far before adding floats to the equation. Once I did that, I got the error:

conflicting implementations of trait `ReadAttribute`

The purpose of this is to be able to pass any type, and then get it cast to any other type (except for string) after reading it from file (the file defines the type). In C++, I know how to do this, since C++ has SFINAE. But in rust, I'm using these traits with macros.

Below, if you uncomment that piece of code, it won't compile because of a conflict.

Am I doing this wrong? How can I get floats to be considered in my design?

Playground

use num_traits::PrimInt;
use num_traits::float::FloatCore;

trait PrimIntTrait: PrimInt + Default {
    fn cast(data: &[u8], type_id: u32) -> Result<Self, Box<dyn std::error::Error>>;
}

trait PrimFloatTrait: FloatCore + Default {
    fn cast(data: &[u8], type_id: u32) -> Result<Self, Box<dyn std::error::Error>>;
}

pub struct HDF5Attribute<T> {
    pub val: T,
}

trait ReadAttribute {
    fn get_attribute_value() -> Result<HDF5Attribute<Box<Self>>, Box<dyn std::error::Error>>;
}

impl ReadAttribute for String {
    fn get_attribute_value() -> Result<HDF5Attribute<Box<String>>, Box<dyn std::error::Error>> {
        return Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData, "error msg")));
    }
}

impl<T: PrimIntTrait> ReadAttribute for T {
    fn get_attribute_value() -> Result<HDF5Attribute<Box<T>>, Box<dyn std::error::Error>> {
        return Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData, "error msg")));
    }
}

//impl<T: PrimFloatTrait> ReadAttribute for T {
//    fn get_attribute_value() -> Result<HDF5Attribute<Box<T>>, Box<dyn std::error::Error>> {
//        return Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidData, "error msg")));
//    }
//}

fn main() {
    println!("Hello world!");
}

Thanks in advance.

1 Like

It looks like you have an identical version of the commented impl right above it

1 Like

Oh, sorry. That's a typo. One of them should be int based on the PrimIntTrait, and the other is for float.

I fixed the question. Sorry for the dumb mistake. 11 PM coding be like...

There's nothing preventing a type from implementing both traits. In this case the compiler wouldn't know which one to choose.
It could work with negative trait bounds but they're not stable yet (or even fully implemented).

For now I'd try to merge PrimIntTrait and PrimFloatTrait into a single trait.

The other option is to use wrapper types:

if you want to be generic over all integer types, you can go through an intermediate struct Int<T : PrimIntTrait>(T) wrapper, and same for struct Float<T : PrimFloatTrait>(T);.

Then, you would implement your ReadAttribute trait for Int<T> and Float<T>, and this way you avoid the conflicting implementations.

The idea is that in the case where you would have a "hybrid" type, then the usage of the wrapper helps disambiguate its intended use case.


That being said, given that the set of integer (and floating) types is finite (and small), that is indeed a place where macros are used a lot.

How can such a merge be done? Simply putting both in one trait creates problems in implementations because int traits do not satisfy float traits.

I am using a macro for the implementation. But I want to add f32 and f64 to that implementation, and all of these should have common traits so that they could go into one macro.

What if you didn't have any bound on the trait:

trait PrimTrait {
    fn cast(data: &[u8], type_id: u32) -> Result<Self, Box<dyn std::error::Error>>;
}
1 Like

That's the solution! Thanks a lot! Shocking how I never needed these bounds anyway.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.