Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Requiring a trait bound on the associated type of an inherited trait

Tags:

rust

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?

like image 599
Tsukki Avatar asked Jun 02 '16 19:06

Tsukki


2 Answers

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.

like image 186
Eric Langlois Avatar answered Oct 30 '22 10:10

Eric Langlois


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, the T: B bound adds a T: 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.

like image 42
malbarbo Avatar answered Oct 30 '22 09:10

malbarbo