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?
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 implementsBar
in 2 variants:i32
andString
:Type dependency graph after blanket impl takes effect:
We end up with 2 different implementations of
Foo
: with baked-ini32
and with baked-inString
. When we write<Baz as Foo>::new()
, compiler can't tell which version ofFoo
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.
The problem is that a single type could implement
Bar<P>
for multiple values ofP
. If you had a structBaz
that implementedBar<i32>
andBar<String>
, which type shouldFoo::new
use forP
?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 theP
type parameter with an associated type.An implementation of
Bar
would look like this:See also: