https://github.com/rust-lang/rust/pull/96709#issuecomment-1181931456
poses the following example:
The distinction between ensures clauses and where clauses.
Given a trait like this:trait WidgetFactory { type Item<T>: Display where T: Debug; }
The Display bound indicates the impl must prove that Item is Display, but the T: Debug bound must be satisfied at the point where the type F::Item<X> is written (i.e., we must show that X: Debug to reference the GAT). This has led to some confusion in the past, and to some extent this is inherent in the syntax.
First, I assume that the above is equivalent to:
type WidgetFactory {
type Item<T: Debug>: Display;
}
Second: Does the above imply that
type Item<T: Debug>: Display;
has different semantics from
type Item<T: Debug> where Self::Item<T>: Display;
https://github.com/rust-lang/rust/pull/96709#issuecomment-1120050703
has several examples, which pnkfelix has reduced down to the following key observations:
#![feature(generic_associated_types)]
pub trait LifetimeGenericConcreteMethod<'a> {
type Item<'c>;
// ~~~~~~~~+~~~~~~
// |
// so far so good; no need to relate 'a and 'c.
//
fn f(&self) -> Self::Item<'static>;
}
pub trait LifetimeGenericConcreteMethodWhereSelf<'a> {
type Item<'c> where Self: 'a;
// ~~~~~~~~+~~~~~~
// |
// required because of just this: \
// ----------+---
fn f(&self) -> Self::Item<'static> where Self: 'a;
}
pub trait LifetimeGenericConcreteMethodWhereItem<'a> {
type Item<'c>;
// ~~~~~~~~+~~~~~~
// |
// look ma, no where clause here
//
fn f(&self) -> Self::Item<'static> where Self::Item<'static>: 'a;
}
pub trait TypeGenericConcreteMethod<'a> {
type Item<T> where T:'a;
// ~~~~+~~~~~
// |
// required because of ...?
//
// (How is this different from LifetimeGenericConcreteMethod?)
//
fn f(&self) -> Self::Item<()>;
}
pub trait TypeGenericParametericMethod<'a> {
type Item<T>;
// ~~~~~~~~~+~~~
// |
// look ma, no where clause here
//
fn f<T>(&self) -> Self::Item<T>; // where Self::Item<T>: 'a;
}
pub trait TypeGenericParametericMethodWhereItem<'a> {
type Item<T>;
// ~~~~~~~~~+~~~
// |
// look ma, no where clause here still
//
fn f<T>(&self) -> Self::Item<T> where Self::Item<T>: 'a;
}
pub trait TypeGenericParametericMethodWhereSelf<'a> {
type Item<T> where Self:'a;
// ~~~~+~~~~~~~~
// |
// required because of this: \
// --------+-----
fn f<T>(&self) -> Self::Item<T> where Self: 'a;
}
There are three axes that seem to be significant for reasons I do not yet understand:
Sometimes a concrete method will force a where clause on the GAT. Compare
TypeGenericConcreteMethod
against TypeGenericParametricMethod
.
Sometimes a where clause constraining Self
in a method will force a
constraint on the GAT, but an analogous where clause constrainting
Self::Item
in the method does not force any analogous constraint on the
GAT. Compare TypeGenericParametricMethodWhereSelf
against
TypeGenericParametericMethodWhereItem
.
But also: a lifetime-generic GAT doesn't seem to get any implicit forced
constraint between it and lifetime params attached to its associating trait,
but a type-generic GAT does. Compare LifetimeGenericConcreteMethod
against TypeGenericConcreteMethod
.
Nota Bene: Please forgive me if this was all previously spelled out in other documents about GATs. They strike me as discrepancies in the user's experience of the feature, but if there are good reasons for all of these discrepancies, then I guess this is a matter of improving our diagnostics or even changing the surface syntax to make such differences in the static semantic more immediately apparent.