Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't I add a blanket impl on a trait with a type parameter?

Consider these two traits:

pub trait Foo {
    fn new(arg: u32) -> Self;
}

pub trait Bar<P>: Foo {
    fn with_parameter(arg: u32, parameter: P) -> Self;
}

I'd like to add the blanket impl:

impl<T: Bar<P>, P: Default> Foo for T {
    fn new(arg: u32) -> Self {
        Self::with_parameter(arg, P::default())
    }
}

But I get the compiler error:

error[E0207]: the type parameter `P` is not constrained by the impl trait, self type, or predicates
 --> src/lib.rs:9:17
  |
9 | impl<T: Bar<P>, P: Default> Foo for T {
  |                 ^ unconstrained type parameter

I think I get this error because I'm violating trait coherence rules, but I don't understand exactly what rule this would break. Why is this pattern not allowed? And, more importantly, can I achieve what I want without getting an error?

like image 769
Others Avatar asked Mar 05 '17 21:03

Others


2 Answers

The problem is that a single type could implement Bar<P> for multiple values of P. If you had a struct Baz that implemented Bar<i32> and Bar<String>, which type should Foo::new use for P?

The only solution is to ensure that a single type cannot implement Bar more than once (if that's not what you want, then you have a flaw in your design!). To do so, we must replace the P type parameter with an associated type.

pub trait Bar: Foo {
    type Parameter;

    fn with_parameter(arg: u32, parameter: Self::Parameter) -> Self;
}

impl<T> Foo for T
where
    T: Bar,
    T::Parameter: Default,
{
    fn new(arg: u32) -> Self {
        Self::with_parameter(arg, T::Parameter::default())
    }
}

An implementation of Bar would look like this:

struct Baz;

impl Bar for Baz {
    type Parameter = i32;

    fn with_parameter(arg: u32, parameter: Self::Parameter) -> Self {
        unimplemented!()
    }
}

See also:

  • Why do I get "the type parameter is not constrained" when creating a blanket implementation for a closure trait (Fn)?
like image 50
Francis Gagné Avatar answered Nov 06 '22 21:11

Francis Gagné


I've broken down and extended Francis's explanation of why the code does not compile. I may not be the smartest kid on the block, but it took me way too long to understand his concise reasoning.

Let's create Baz, which implements Bar in 2 variants: i32 and String:

struct Baz;

impl Bar<i32> for Baz { /* ... */ }

impl Bar<String> for Baz { /* ... */ }

Type dependency graph after blanket impl takes effect:

           -> trait Bar<i32>    -> trait Foo (with i32 baked-in)
struct Baz
           -> trait Bar<String> -> trait Foo (with String baked-in)

We end up with 2 different implementations of Foo: with baked-in i32 and with baked-in String. When we write <Baz as Foo>::new(), compiler can't tell which version of Foo we mean; they are indistinguishable.

The rule of a thumb is that trait A can have blanket implementation for trait B only if trait A is generic over all generic parameters of trait B.

like image 23
CodeSandwich Avatar answered Nov 06 '22 21:11

CodeSandwich