DEV Community

Sean Policarpio
Sean Policarpio

Posted on • Updated on

How to use more than one Trait implementation while avoiding conflicts

Problem

Say you initially have code like this:

struct DeserializeError(String);
trait Deserialize<T> {
    fn into_type(self) -> Result<T, DeserializeError>
}

struct Foo {}
impl Deserialize<Foo> for Vec<u8> {
    fn into_type(self) -> Result<Foo, DeserializeError> {
        unimplemented!() // Vec<u8> -> Foo
    }
}

struct Bar {}
impl Deserialize<Bar> for Vec<u8> {
    fn into_type(self) -> Result<Bar, DeserializeError> {
        unimplemented!() // Vec<u8> -> Bar
    }
}

and later down the road, you need to write a generic implementation of Deserialize that requires the use of Deserialize<Foo>, Deserialize<Bar>, and whatever other implementations exist in your code base.

The problem I encountered was when I initially tried to do the following:

struct Wrapper<T> { foo: Foo, t: T }

// first 4 bytes are Foo, the rest will make up T
impl<T> Deserialize<Wrapper<T>> for Vec<u8> {
    fn into_type(self) -> Result<Wrapper<T>, DeserializeError> {
        self[0..=3].to_vec().into_type().and_then(|foo: Foo| {
            self[4..].to_vec().into_type().map(|some_t: T| {
                Wrapper { foo, t: some_t }
            })
        })
    }
}

Unfortunately, you'll get the following error:

|     self[4..].to_vec().into_type().map(|some_t: T| {
|                                    ^^^^^^^^^^^^^^^^^^ the trait `Deserialize<T>` 
|     is not implemented for `std::vec::Vec<u8>`

The compiler wonderfully suggested the following advice though:

consider adding a `where std::vec::Vec<u8>: Deserialize<T>` bound

Ok, so let's try that:

impl<T> Deserialize<Wrapper<T>> for Vec<u8>
    where Vec<u8>: Deserialize<T> {
    fn into_type(self) -> Result<Wrapper<T>, DeserializeError> {
        self[0..=3].to_vec().into_type().and_then(|foo: Foo| {
            self[4..].to_vec().into_type().map(|some_t: T| {
                Wrapper { foo, t: some_t }
            })
        })
    }
}

But now we get the following compiler error:

   = note: expected type `Foo`
              found type `T`

Why? Because the where bound has stated that Vec<u8> in this scope must implement Deserialize<T>, and based on my best guess, has explicitly enforced only that implementation into scope as well. So my problem is that although I don't have conflicting implementations of the trait for Vec<u8>, I had conflicting use of the trait.

Solution

This took me awhile to find, but the solution was in the second edition of "The Rust Programming Language" book. In the Advanced Traits section, the key was the subsection "Fully Qualified Syntax for Disambiguation: Calling Methods with the Same Name".

Although this section was regarding explicitly stating which method to use from multiple traits with the same name (conflicting method names, not trait names), it did the trick here.

impl<T> Deserialize<Response<T>> for Vec<u8>
    where Vec<u8>: Deserialize<T> {
    fn into_type(self) -> Result<Wrapper<T>, DeserializeError> {
        Deserialize::<Foo>::into_type(self[0..=3].to_vec()).and_then(|foo: Foo| {
            Deserialize::<T>::into_type(self[4..].to_vec()).map(|some_t: T| {
                Wrapper { foo, t: some_t }
            })
        })
    }
}

I still retain the where bound so that the implementation is in scope by the compiler, but by using the fully qualified syntax—including the generic type—I'm telling the compiler exactly which implementations I actually want to use. And of course, it compiles. From the perspective of a Scala developer, this is kind of like temporarily skipping implicit class conversion and manually calling the implementation you want.

originally posted as a gist.

Top comments (0)