Summary
Allow adding generic parameters to associated types in a backward compatible fashion.
Motivation
With generic associated types (GATs) on track for stabilization, I would like to open the discussion on how to leverage them in the standard library.
In the original RFC for GATs, lending iterators served as the main motivating example.
Unlike the current Iterator
trait, a LendingIterator
may yield items whose lifetime is tied to the iterator itself.
For the currently unstable Stream
trait, a similar lending variant has been discussed.
The downside of adding a lending variant for each trait is increased duplication and complexity. Especially Rust new-comers may find it difficult to choose between the classic and lending variants. Since non-lending iterators can be implemented in terms of the lending trait, I propose to add a generic lifetime parameter to the associated type of the existing trait, instead of introducing new traits. This pre-RFC outlines how such a change could be possible while retaining backward compatibility with existing implementations.
Guide-level explanation
The existing Iterator
trait (and potentially other traits) is modified to allow producing values that may have references into the iterator itself.
This is possible by making the associated Item
type generic over the lifetime of the iterator.
To ensure backward compatibility, a default is specified for the generic parameter.
A GATified version of the Iterator
trait would then look as follows:
trait Iterator {
type Item<'s = 'static> where Self: 's;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
Implementations, trait bounds and traits with Iterator
as a supertrait may omit the generic parameter when referring to the associated Item
type.
Reference-level explanation
We consider how the GATified version of the Iterator
trait from above would affect implementations, trait bounds and supertraits.
Trait Implementations
impl<'a, T> Iterator for Iter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
/* ... */
}
}
In trait implementations, the generic parameters in the definition of an associated types can be omitted if they are not used.
This is equivalent to explicit dummy parameters.
In the example above, type Item = &'a T
would be equivalent to
type Item<'dummy> where Self: 'dummy = &'a
When referring to an associated type, generic parameters can be omitted if a default is specified.
Hence, Self::Item
and <Foo as Iterator>::Item
would be equivalent to Self::Item<'static>
and <Foo as Iterator>::Item<'static>
, respectively.
In the example above, the substituted value is actually irrelevant, since it is not used in the definition of the associated type.
However, the default value is important in the case of supertraits, as is explained next.
Supertraits
trait DoubleEndedIterator: Iterator {
fn next_back(&mut self) -> Option<Self::Item>;
}
In case a GATified trait is a supertrait of an existing trait, the associated type may be referred to without generic parameters.
In this case, the defaults defined in the supertrait will be used.
In the example above, Self::Item
would be equivalent to Self::Item<'static>
.
Trait Bounds
trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter;
}
While the IntoIterator
may also be a candiate for GATification, here we consider how a non-GATified version would interact with a GATified Iterator
trait.
In trait bounds, the generic parameters of an associated type may be omitted if unused.
Thus, the bound Iterator<Item = Self::Item>
in the example above would be equivalent to
for<'dummy> Iterator<Item<'dummy> = Self::Item>
Drawbacks
- The
Iterator
trait becomes more complex
Rationale and alternatives
- Add new GATified traits in addition to the existing ones
- The default for generic lifetime parameters could always be
'static
Prior art
- Discussion in issue #44265
Unresolved questions
Which traits should be GATified?
How about, for example, GATified closure traits:
trait Fn<Args> {
type Output<'s>;
fn call(&self, arg: Args) -> Self::Output<'_>;
}
(see also this post)