Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing a trait for reference and non reference types causes conflicting implementations

Tags:

rust

I'm trying to create a trait and provide one implementation for all non-reference types, and another for all reference types.

This fails to compile:

trait Foo {}
impl<T> Foo for T {}
impl<'a, T> Foo for &'a mut T {}

This fails with the error

error[E0119]: conflicting implementations of trait `Foo` for type `&mut _`:
 --> src/main.rs:3:1
  |
2 | impl<T> Foo for T {}
  | -------------------- first implementation here
3 | impl<'a, T> Foo for &'a mut T {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `&mut _`

Strangely enough, this works:

trait Bar {}

impl<T> Bar for T
where
    T: Clone,
{}

impl<'a, T> Bar for &'a mut T
where
    T: Clone + 'static,
{}

Why does the version with the Clone constraint work, and how can I make it work without it?

like image 409
bearclaw Avatar asked Dec 19 '22 01:12

bearclaw


2 Answers

As you have learned, a generic T can be anything¹, so the Foo impls overlap (conflict) whenever T in the first impl is &'a mut U, because the second impl also covers that case (when T is U).

The Clone version works simply because &mut references never implement Clone, so there's no overlap between T where T: Clone and &'a mut T.² If you try to implement Bar for immutable (&) references, you will have a conflict again, because immutable references do implement Clone.

[H]ow can I make it work without it?

If by "it" you mean one implementation for reference types and another, different one for non-reference types, that's not possible in Rust for the same reason you can't implement a trait one way for structs and another way for enums: there simply is no way to express it (in current Rust).

One common pattern that might work for you is implementing your trait individually for whatever non-reference types you need, and then adding a "blanket impl" that covers any reference to a type for which the trait is already implemented, e.g.:

impl Foo for u32 { ... }
impl Foo for i32 { ... }
impl<'a, T> Foo for &'a T where T: Foo + 'a { ... }
impl<'a, T> Foo for &'a mut T where T: Foo + 'a { ... }

¹ Well, anything that is Sized, at least. You have to add ?Sized if that's not what you want.

² The where T: Clone + 'static clause doesn't matter, because &'a mut T will never be Clone whether T itself is or not.

like image 55
trent Avatar answered Jun 08 '23 22:06

trent


The reason for the Clone version works is because the types that the trait is being implemented for are no longer conflicting on the implementation.

Take the first example and add a default implementation.

trait Foo {
    fn hi(&self){
        println!("Hi");
    }
}

And then we implement Foo for all of type T with impl<T> Foo for T {} this actually implements enough for us to use a reference to our types and use the Foo trait. For Example:

fn say_hi<'a>(b: &'a mut Foo){
    b.hi();
}

fn main(){
    let mut five = 5;

    five.hi(); // integer using Foo
    say_hi(&mut five); // &'a mut Foo
}

To answer the second part of you question, you didn't need the second implement of impl<'a,T> Foo for &'a mut T {} because impl<T> Foo for T {} was enough to give you what you were looking for.

Now that we have seen that the first example works without the second implement it starts to make sense that the example using Clone works because you are implementing for a subset of types T that are Clone and a different subset of types &'a mut T that are Clone+static

like image 30
Daniel Wilkins Avatar answered Jun 08 '23 21:06

Daniel Wilkins