Positional Associated Types
See original GitHub issuePositional Associated Types
Proposal
Summary and problem statement
It should be possible to abbreviate Future<Output=T>
as Future<T>
.
Motivation, use-cases, and solution sketches
Many of the most-used traits in the rust programming language specify a single associated type:
Iterator<Item=T>
Future<Output=T>
FromStr<Err=T>
Deref<Target=T>
Stream<Item=T>
- and so on
The impl Trait
syntax is intended to make instances of such traits easy to pass and return. But this can be quite verbose in practice, because each of these traits must be written with the associated type explicitly spelled out:
fn deltas(iter: impl Iterator<Item=f32>) -> impl Iterator<Item=f32> {
iter.array_windows().map(|[a, b]| b - a)
}
Worse, it may not be clear to incoming Rust programmers why they have to write Iterator<Item=f32>
instead of Iterator<f32>
. After all, the generics of traits like From<T>
and types like Option<T>
are not named. It can take many months for the difference between generic parameters and associated types to click, and in that time users probably guess at random which they should use for any given trait.
It is worth investigating whether these common cases can be abbreviated:
pub trait Future {
#[positional]
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
impl Logger {
pub fn log_outcome(task: impl Future<String>) {
...
}
}
This should also apply to the dyn Future<T>
and F: Future<T>
syntaxes.
The Iterator<T>
and Future<T>
traits would almost certainly be the first to have positional associated types (PATs) stabilized. But the feature could also be beneficial for some traits with multiple associated types:
pub trait Try: FromResidual<Self::Residual> {
#[positional]
type Output;
#[positional]
type Residual;
fn from_output(output: Self::Output) -> Self;
fn branch(self) -> ControlFlow<Self::Residual, Self::Output>;
}
impl Logger {
pub fn log_outcome(task: impl Future<impl Try<impl Display, impl Display>>) {
...
}
}
Note this proposal would not immediately include any of the following use cases:
// PATs in trait implementations
impl Iterator<f32> for MyIterator { }
// Mixing type parameters and PATs
trait MyTrait<T> {
#[positional]
type Extra;
}
// Named type parameters
fn get(&self, key: &impl Borrow<Borrowed=str>) { }
Links and related work
- Discussion in the async foundations Zulip stream
- The
Fut
alias trait - Rules for positional and keyword arguments to Python functions
- Typescript’s debate over named type parameters
Initial people involved
- Owner: Sam Sartor
- Liaison: Josh Triplett
What happens now?
This issue is part of the lang-team initiative process. Once this issue is filed, a Zulip topic will be opened for discussion, and the lang-team will review open proposals in its weekly triage meetings. You should receive feedback within a week or two.
This issue is not meant to be used for technical discussion. There is a Zulip stream for that. Use this issue to leave procedural comments, such as volunteering to review, indicating that you second the proposal (or third, etc), or raising a concern that you would like to be addressed.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:17
- Comments:11 (10 by maintainers)
While I have felt the pain of wishing for a shorthand like
Iterator<u32>
, I am concerned that this change is going to make the distinction between associated types and generic parameters harder for people to understand. I would like to think it’s possible for us to empirically evaluate this to some degree, though I also do feel that prototyping and using it for a while is a good way to gain experience.(That said, I am also skeptical of the motivation, or at least one motivation I have heard: specifically, I don’t think that having generators have a return type like
-> impl Iterator<Item = u32>
is necessarily a good idea, given the precedent set byasync fn
(and, in fact, generators share the property that they capture all their arguments, which to me was a crucial reason forasync fn
to have the type they do).)I don’t see this concern as blocking exploration, but I wanted to note it down, and I would expect to spend some time discussing it before we moved to any sort of RFC here.
Summary of concerns gathered from lang team meeting on 2021-11-16 and from the zulip thread:
Trait<Assoc>
) implies it is a breaking change to ever add a defaulted generic param.Iterator<Item=f32>
instead ofIterator<f32>
, and attempts to address that by unifying the two syntaxes.However, it is important to stress that that this is a proposal to solve a problem, not a commitment to a specific solution. The syntax could change, which might address some of the concerns above. In that spirit, here is a list of alternative solutions suggested in Zulip thread:
trait Fut<T> = Future<Output=T>;
; use site:fn foo() -> impl Fut<u32>
fn foo() -> impl Future<_=u32>
(no change to def’n site, it remainstrait Future { type Output; }
).trait Future<O == Self::Output> { type Output; }
; use site:fn foo() -> impl Future<u32>