I have a trait Foo
inheriting from another trait Bar
. Bar
has an associated type Baz
. Foo
constrains Baz
such that Baz
must implement Hoge
.
trait Hoge {}
trait Bar {
type Baz;
}
trait Foo: Bar where Self::Baz: Hoge {}
However, when I define a generic function requiring the generic type T
to implement Foo
,
// [DESIRED CODE]
fn fizz<T: Foo>(buzz: T) {
// ...
}
rustc
complains with EO277
unless I constrain T
explicitly:
fn fizz<T: Foo>(buzz: T) where T::Baz: Hoge {
// ...
}
I do not understand why I need to do this. I would like to be able to write [DESIRED CODE]
. What is the recommended way to do this?
It is possible to work around this behaviour by using another associated type in Foo
since the compiler accepts implicit bounds when part of the associated type definition (playground):
trait Foo: Bar<Baz = Self::HogeBaz> {
type HogeBaz: Hoge;
}
The new associated type can be hidden in a helper trait to avoid having to include it in every implementation. Full example (with renaming for clarity) (playground)
trait Bound {
fn bound();
}
trait Trait {
type Type;
}
trait BoundedTypeHelper: Trait<Type = Self::BoundedType> {
type BoundedType: Bound;
}
impl<T> BoundedTypeHelper for T
where
T: Trait,
Self::Type: Bound,
{
type BoundedType = Self::Type;
}
trait UserTrait: BoundedTypeHelper {}
fn fizz<T: UserTrait>() {
T::Type::bound()
}
I am with you in thinking that the original where-based bound ought to be treated as part of the trait definition and applied implicitly. It feels very arbitrary that bounding associated types inline works but where
clauses do not.
Sadly (or not), you have to repeat the bounds.
Last year I opened a issue thinking that the type checker was being inconsistent. The code is similar to yours.
@arielb1 closed the issue and said that this was the intended behavior and gave this explanation:
The thing is that we don't want too many bounds to be implicitly available for functions, as this can lead to fragility with distant changes causing functions to stop compiling. There are basically 3 kinds of bounds available to a function:
- bounds from explicit where-clauses - e.g.
T: B
when you have that clause. This includes the "semi-explicit"Sized
bound.- bounds from supertraits of explicit where-clauses - a where-clause adds bounds for its supertraits (as
trait B: A
, theT: B
bound adds aT: A
bound).- bounds from the lifetime properties of arguments (outlives/implicator/implied bounds). These are only lifetime bounds, and irrelevant for the current problem. rust-lang/rfcs#1214 involved them a great deal.
If your bound isn't in the list, you will have to add it explicitly if you want to use it. I guess this should be a FAQ entry.
Today I opened an issue to request that this information to be added to the docs.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With