Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Positional Associated Types #126

Closed
samsartor opened this issue Nov 12, 2021 · 11 comments
Closed

Positional Associated Types #126

samsartor opened this issue Nov 12, 2021 · 11 comments
Labels
final-comment-period The FCP has started, most (if not all) team members are in agreement major-change Major change proposal T-lang to-announce Not yet announced MCP proposals

Comments

@samsartor
Copy link
Contributor

Positional 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

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.

@samsartor samsartor added major-change Major change proposal T-lang labels Nov 12, 2021
@rustbot
Copy link
Collaborator

rustbot commented Nov 12, 2021

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.

@rustbot rustbot added the to-announce Not yet announced MCP proposals label Nov 12, 2021
@joshtriplett
Copy link
Member

@rustbot second

I'm willing to be the liaison for this.

@rustbot rustbot added the final-comment-period The FCP has started, most (if not all) team members are in agreement label Nov 12, 2021
@joshtriplett
Copy link
Member

Summary of the current state of this: some folks on the lang team have concerns about this, but they're not blocking concerns, and those same folks have explicitly said they don't object to experimentation on nightly, as long as we carefully evaluate those concerns after we have something to experiment.

@pnkfelix plans to provide more detail on those concerns.

@samsartor
Copy link
Contributor Author

samsartor commented Dec 3, 2021

after we have something to experiment.

I have to focus on school for another couple of weeks but then I'd be thrilled to throw a prototype together in rustc.

@nikomatsakis
Copy link
Contributor

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 by async fn (and, in fact, generators share the property that they capture all their arguments, which to me was a crucial reason for async 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.

@pnkfelix
Copy link
Member

pnkfelix commented Dec 28, 2021

Summary of concerns gathered from lang team meeting on 2021-11-16 and from the zulip thread:

  • sem. ver issue: supporting PAT on a trait (via the given syntaxTrait<Assoc>) implies it is a breaking change to ever add a defaulted generic param.
    • Concern: So, even though PAT's are opt-in, there is a huge back compat promise. How much use is this expected to get, and does that justify a language change (as opposed to some alternative solution; some are outlined below)
  • the proposal says it is unclear to incoming Rust programmers why they have to write Iterator<Item=f32> instead of Iterator<f32>, and attempts to address that by unifying the two syntaxes.
    • Concern: We should not assume that conflating two different things, or hiding the difference between them, improves learning curves.
    • Counterpoint: Forcing people to pay attention to a difference like this is not necessarily a win either.
  • Concern (admittedly vague): in future scenarios, the distinction between generics and associated types may become important in new ways.

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:

@joshtriplett
Copy link
Member

@pnkfelix Great summary, thank you!

@joshtriplett
Copy link
Member

@samsartor When you're ready to start experimenting on this, please take a look at the summary from @pnkfelix, and talk a bit about what approach you want to try to resolve these open questions.

@samsartor
Copy link
Contributor Author

@joshtriplett Thanks for the ping! Experimentation wise, the Fut<T> and Iter<T> trait aliases are the absolute simplest to implement. I'm tempted to PR them to core right away, just so we can see how much people want to use them and what concerns show up. After that I'm guessing the #[positional] attribute and/or name elision would easiest to play with but I'll see as I dig into that area of rustc.

I think the difficulty there would be collecting meaningful feedback, so we can actually make progress on resolving the tradeoffs here. Assuming I get some experimental branches done pretty soon, what do you think the next steps should be? Is the goal to write a blog post with concrete examples, or just let this feature flag simmer on nightly until people collectively develop an opinion?

@joshtriplett
Copy link
Member

Following up on this: we can work with a researcher that @nikomatsakis has arranged to try to come up with criteria and process to evaluate this.

@nikomatsakis
Copy link
Contributor

Hey folks! I'm going to close this issue, but I wanted to note that -- per our new process -- this might be a great candidate for a lang-team experiment.

I myself remain a bit conflicted here -- I recognize that the current associated type syntax can be ponderous, while being nervous that having more options will lead to more confusion (plus we would now need a style guideline for whether to use the default and when...).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
final-comment-period The FCP has started, most (if not all) team members are in agreement major-change Major change proposal T-lang to-announce Not yet announced MCP proposals
Projects
None yet
Development

No branches or pull requests

5 participants