Search code examples
genericsrusttraitstype-parameter

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?


Solution

  • 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: