When one write trait A: B where "some bound"{}
logically one could say that the compiler should apply the rule T:A => "some bound". But it does not. It just check "some bound" when A is implemented, and that is all.
The consequence is that this code, which use a where clause, does not compile:
trait A {
type B;
fn f(&self, v: u32);
}
trait C: A
where Self::B: Into<u32>,
{}
fn f<T: C>(v: T, arg: T::B) {
v.f(arg.into()) //Error B does not implement Into<u32>
}
On the other hand this code, and the following one do compile:
trait A2 {
type B;
fn f(&self, v: u32);
}
trait C2: A2<B: Into<U32>> {}
fn f2<T: C2>(v: T, arg: T::B) {
v.f(arg.into())
}
or:
trait A2 {
type B: Into<u32>;
fn f(&self, v: u32);
}
trait C2: A2 {}
fn f2<T: C2>(v: T, arg: T::B) {
v.f(arg.into())
}
The fact the first piece of code does not compile, is normal? Is this a bug? Or a limitation of rustc?
This looks like a case of the venerable “implied bounds”, or “where clauses are only elaborated for supertraits, and not other things” issue that just turned ten in January. It’s a compiler limitation, but adding more implicitness has code clarity and API semver implications.
It's normal / how it's always been. It's not a bug as far as I'm concerned, because making existing additional bounds implied creates a SemVer hazard. That is, if all bounds become implied, it's not longer a minor change to remove a bound which is, today, not implied. IMNSHO that should be a choice the trait author makes, and that ability -- in cases which they have the choice today -- should not be taken away from them.
But it can be considered a limitation of the language. That is, I feel that trait authors should have the explicit choice for any bounds. Or in other words, implied bounds should be opt-in.
Here's an RFC about implied bounds.
As it happens, associated trait bounds is the one use case where, today, the trait author can opt into implied bounds, or choose not to opt in to implied bounds. If you want it to be implied, use an inline associated type bound instead of a where
clause:
trait C: A<B: Into<u32>>
Consider three cases: where
clause, implied inline bound, or no associated type bound at all. You can go from the where
clause to the other two cases without a major version bump. Going from the other two cases to any other case is a breaking change.
2 Likes