Conflicting implementations and Unpin

Apologies if this is a basic question that's already answered somewhere, I couldn't find it with a search.

I have a trait Foo, whose behavior should not be customizable by the user for structs are Unpin. This is because it doesn't make a lot of sense to customize behavior of Foo for Unpin types, and thus if the user is trying to do so, it is probably an error as they probably forgot to add a std::marker::PhantomPinned to their struct.

So I wrote what I thought was correct:

trait Foo { fn foo() {}}

impl<T> Foo for T where T : Unpin {}

Foo is implemented for Unpin` types, and indeed if I do:

struct Bar {}

fn main() {
    <Bar as Foo>::foo()
}

This compiles no problem!

The problem arises when I want to implement Foo for a !Unpin type:

struct Baz { _pinned: std::marker::PhantomPinned }

impl Foo for Baz { fn foo() { println!("Customized!") } }

The above yields:

error[E0119]: conflicting implementations of trait `Foo` for type `Baz`:
  --> src/main.rs:10:1
   |
4  | impl<T> Foo for T where T : Unpin {}
   | --------------------------------- first implementation here
...
10 | impl Foo for Baz { fn foo() { println!("Customized!") } }
   | ^^^^^^^^^^^^^^^^ conflicting implementation for `Baz`

If I remove the impl Foo for Baz and try to use the blanket implementation, however...

error[E0277]: `std::marker::PhantomPinned` cannot be unpinned
  --> src/main.rs:11:5
   |
2  | trait Foo { fn foo() {}}
   |             -------- required by `Foo::foo`
...
11 |     <Baz as Foo>::foo()
   |     ^^^^^^^^^^^^^^^^^ within `Baz`, the trait `std::marker::Unpin` is not implemented for `std::marker::PhantomPinned`
   |
   = note: required because it appears within the type `Baz`
   = note: required because of the requirements on the impl of `Foo` for `Baz`

Having an error here is to be expected, since Baz is not Unpin.
However the message of the error informs me that the situation is not as simple as I thought.
It seems that having a PhantomPinned field makes "being Unpin" to fail for Baz, but that it doesn't make Baz not Unpin by itself, hence the conflicting implementations?

Is there a way around this particular condition? It is really important to achieve this for my use case.

By contrast, if I blanket-implement Foo for a different autotrait (such as Copy), I can do the above and customize the behavior only for non Copy structs:


trait Foo { fn foo() {println!("Default")}}

impl<T> Foo for T where T : Copy {}

#[derive(Copy, Clone)]
struct Bar {}

struct Baz { }

impl Foo for Baz { fn foo() { println!("Customized") } }

fn main() {
    <Bar as Foo>::foo(); // => Default
    <Baz as Foo>::foo() // => Customized
}

I would expect the same to work for Unpin. Is there any workaround to this?

This is because adding traits to types is not concidered a breaking change, so it has to assume PhantomPinned could be Unpin in the future, which would make your type Unpin.

It works for Copy because changes in other crates can't break you code; you have to add a derive to your code to break it.

2 Likes

I see; any workaround then? It looks like explicitly declaring your type to be !Unpin is unstable at the moment.

I'm not aware of any workaround.

Ok, thank you for your time. I will think about other approaches

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