Implementing a trait over another trait - spurious warnings about "dyn"

I'm not understanding exactly what the compiler is trying to tell me with this code:
Playground: Rust Playground

trait Vec2<T> where T: Copy {
}

struct Vector2<T> where T: Copy {
    pub x: T,
    pub y: T,
}

impl<T> Vec2<T> for Vector2<T> where T: Copy {}

trait Pixel {
    const COMPONENTS: usize;
}

impl<T> Pixel for Vec2<T> where T: Copy {
    const COMPONENTS: usize = 2;
}

fn main() {
    println!("{}", Vec2::<i32>::COMPONENTS);
}

Warnings:

warning: trait objects without an explicit `dyn` are deprecated
      --> src/main.rs:15:19
       |
    15 | impl<T> Pixel for Vec2<T> where T: Copy {
       |                   ^^^^^^^ help: use `dyn`: `dyn Vec2<T>`
       |
       = note: `#[warn(bare_trait_objects)]` on by default

I'm trying to specify that I want to implement Pixel for any type that implements Vec2. It seems to work, but I'm not sure how to silence those warnings. Types that implement Vec2 and Pixel will always be sized, and never be a trait object - they're simple POD structs.

What you're looking for is:

impl<T, U> Pixel for T where T: Vec2<U>

Thanks! Unfortunately that gives me:

error[E0207]: the type parameter `U` is not constrained by the impl trait, self type, or predicates
  --> src/main.rs:16:9
   |
16 | impl<T, U> Pixel for T where T: Vec2<U>, U: Copy {
   |         ^ unconstrained type parameter

error: aborting due to previous error

Perhaps you want the T argument in the Vec2 trait to actually be an associated type. E.g.

trait Vec2 {
    type Item: Copy;
}

struct Vector2<T> where T: Copy {
    pub x: T,
    pub y: T,
}

impl<T> Vec2 for Vector2<T> where T: Copy {
    type Item = T;
}

trait Pixel {
    const COMPONENTS: usize;
}

impl<T> Pixel for T where T: Vec2 {
    const COMPONENTS: usize = 2;
}

fn main() {
    println!("{}", Vector2::<i32>::COMPONENTS);
}
1 Like

That works! Thank you. Still not understanding exactly why I had the warnings on the first one, or why OptimisticPeach's solution didn't work, but I'll take it.

impl<T> Pixel for Vec2<T> where T: Copy

This is read as:

Implement given any T, the trait Pixel on the trait Vec2<T> where T implements Copy.

Notice how I said "on the trait Vec<T>"?

That's implementing it on the trait, but traits aren't types, and therefore it's assuming you're doing trait objects. It'll implicitly change it to:

impl<T> Pixel for dyn Vec2<T> where T: Copy

With a type parameter like that, a type Foo could actually have multiple implementations Foo: Vec2<Bar> and Foo: Vec2<Baz> with different parameters. Then these would result in multiple implementations of Foo: Pixel (one based on Foo: Vec2<Bar> and one based on Foo: Vec2<Baz>, both through the generic implementation impl<T, U> Pixel for T where T: Vec2<U>). But there can only be one implementation of Pixel for Foo, not multiple conflicting ones. To see some actual conflict, think of an implementation like

impl<T, U> Pixel for T where T: Vec2<U>, U: Copy {
    const COMPONENTS: usize = std::mem::size_of::<U>();
}

and consider some

struct Foo;
#[derive(Copy, Clone)]
struct Bar(i32);
#[derive(Copy, Clone)]
struct Baz(i64);

impl Vec2<Bar> for Foo {}
impl Vec2<Baz> for Foo {}

// now, what is `Foo::COMPONENTS` equal to?

Of course, your implementation doesn’t really depend on U at all, so there is no actual conflict, but there is no way for the compile to know that.

1 Like

Ah I see I'd assumed it was implementing it over the trait with that syntax, as in "impl Pixel for any type that impls Vec2"

Thanks for all the help so far! Now I have a new problem. In my real code, Vec2 is actually defined in another crate that the crate that defines Pixel depends on. So when I change my code to use an associated type, I now get this error:

error[E0119]: conflicting implementations of trait `frame_buffer::Pixel` for type `f32`:
   --> src/frame_buffer.rs:465:1
    |
446 |   impl Pixel for f32 {
    |   ------------------ first implementation here
...
465 | / impl<T> Pixel for T
466 | | where
467 | |     T: Vec2,
468 | | {
...   |
471 | |     const CHANNEL_STRIDE: usize = T::CHANNEL_STRIDE;
472 | | }
    | |_^ conflicting implementation for `f32`
    |
    = note: upstream crates may add a new impl of trait `imath_traits::Vec2` for type `f32` in future versions

I understand that the compiler is telling me here, but I don't know how to work around it. Is there any way to specify in the imath_traits crate that f32 can never impl Vec2? Or any other way to tell the compiler not to worry about it?

EDIT: looks like negative trait bounds would do what I want but they're not implemented yet.

No, or at least, not yet. In the (possibly far) future, we might eventually get something like negative trait implementations stabilized.

The answer is “no” on this as well. Rust takes trait coherence / orphan rules seriously because they are even relevant for soundness (although their main point is to avoid breakage, and AFAICT soundness is not an issue in your particular case).

Since both crates seem to be your own, perhaps it is reasonably possible to move those two traits into the same crate somehow.

Thanks. Yeah semantically it doesn't make a huge amount of sense to move Pixel into the upstream crate, but pragmatically it might be.

I suppose the other option would just be to forget trying to do the blanket impl for Pixel over Vec2 and just explicitly specify all the relevant impls. I'll have to try both out and see how they feel.

Thanks for the help!