Open udoprog opened 3 years ago
So I encountered that the proposed workaround might not be as workable as I'd like, in that it requires the caller to specify that bound at each item where the associated type is used, like so:
#![feature(generic_associated_types)]
trait Collection {
type Iter
where
Self::Iter: IntoIterator,
<Self::Iter as IntoIterator>::Item: std::fmt::Debug;
fn iter(&self) -> Self::Iter
where
Self::Iter: IntoIterator,
<Self::Iter as IntoIterator>::Item: std::fmt::Debug;
}
And in my mental model I think this makes sense in general with GATs, because the item (fn iter
) might specify additional bounds on generic parameters. But it also means that it's a difference (intentional or not) with inheritance-style bounds like type Iter<'a>: IntoIterator
which does not need to be specified every time it's used.
Like this:
#![feature(generic_associated_types)]
trait Collection {
type Iter<'a>: IntoIterator;
fn iter(&self) -> Self::Iter<'_>;
}
Compared to this:
#![feature(generic_associated_types)]
trait Collection {
type Iter<'a>
where
Self::Iter<'a>: IntoIterator;
fn iter<'a>(&'a self) -> Self::Iter<'a>
where
Self::Iter<'a>: IntoIterator;
}
Ah, so. There indeed is a difference between writing
trait Foo {
type Bar<T>: Debug;
}
and
trait Foo {
type Bar<T> where Self::Bar<T>: Debug;
}
Specifically, the bounds type Bar<T>: Debug
must be proved by the impl, whereas the where clauses must be provable by the type.
The well-formedness rules for these are generally laid out pretty well in the Chalk book (http://rust-lang.github.io/chalk/book/clauses/wf.html). Though, this is the "ideal" world and might not be completely implemented in rustc this way (these in some ways rely on implied bounds and such).
But, I wanted to point out that for
trait Trait<P1...> where WC_trait {
type Assoc<P2...>: Bounds_assoc where WC_assoc;
}
we generate the following goal:
forall<P1...> {
if (FromEnv(WC_trait)) {
WellFormed(InputTypes(WC_trait)) &&
forall<P2...> {
if (FromEnv(WC_assoc)) {
WellFormed(InputTypes(Bounds_assoc)) &&
WellFormed(InputTypes(WC_assoc))
}
}
}
}
This is essentially saying that "if we assume the where clauses on the trait hold, then we require that the types present on those where clauses be well formed" and "if we assume the where clauses on the associated type hold too, then we require that the types in the bounds and where clauses on the associated type are well formed"
For this example:
trait Collection {
type Iter<'a>: IntoIterator
where
<Self::Iter<'a> as IntoIterator>::Item: std::fmt::Debug;
}
there are no where clauses on the trait, so that bit is straightforward. But then we say that if <Self::Iter<'a> as IntoIterator>::Item: std::fmt::Debug
holds then we require that Self::Iter<'a>: IntoIterator
is well-formed. Importantly, the rules as-written don't take into account the Iter<'a>: IntoIterator
bound.
At first thought, this feels wrong to me: given that we have the IntoIterator
bound, should we always satisfy that (and so we don't need to repeat that in the where clause)? Not sure :) This is something to think about.
GATs issue triage: not blocking. See my previous comment. This is probably code that shouldn't be written as-is. But nonetheless is not really a GATs issue.
I tried this code:
This fails to compile with:
Playground
aka/rrevenantt
on Discord discovered the following workaround, which to me seems like it should be treated the same as above. The only difference is the location of the bound - one using "inheritance style", the other as part of a where clause:It's worth noting that the same issue exists without GATs. But
where
clauses on associated types are also feature gated undergeneric_associated_types
.This fails to compile:
However this works on stable (lifting the bound to the trait itself):