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

Syntax for precise capturing: impl Trait + use<..> #125836

Open
traviscross opened this issue Jun 1, 2024 · 13 comments
Open

Syntax for precise capturing: impl Trait + use<..> #125836

traviscross opened this issue Jun 1, 2024 · 13 comments
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-precise_capturing `#![feature(precise_capturing)]` finished-final-comment-period The final comment period is finished for this PR / Issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. to-announce Announce this issue on triage meeting

Comments

@traviscross
Copy link
Contributor

traviscross commented Jun 1, 2024

For precise capturing (#123432), we need to decide which syntax to adopt.

The original two, left as an open question in the RFC, were:

  1. impl use<..> Trait
    • This syntax is used throughout the RFC.
  2. use<..> impl Trait
    • This syntax is the worthy challenger.

(See the alternatives section in the RFC for a detailed comparative analysis of these options. In particular, so as to reduce duplication, please read that section carefully before commenting here.)

However, in the design meeting on 2024-06-05, as described below, we settled on placing use<..> within the list of bounds, e.g.:

fn foo<'a>() -> impl Sized + use<'a> {}

This issue is to track the resolution of the open question on syntax left in the RFC.

Tracking:

@traviscross traviscross added T-lang Relevant to the language team, which will review and decide on the PR/issue. F-precise_capturing `#![feature(precise_capturing)]` labels Jun 1, 2024
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jun 1, 2024
@traviscross traviscross removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Jun 1, 2024
@traviscross
Copy link
Contributor Author

traviscross commented Jun 6, 2024

We discussed this in the lang design meeting today:

The conclusion of the team is that we'll make use<..> a bound. That is, we'll support impl use<..> + Trait, impl Trait + use<..>, etc.

For now, we will support at most one such bound in a list of bounds, and semantically we'll only support these bounds in the item bounds of RPIT-like impl Trait opaque types (i.e., in the places discussed in the RFC).

We were interested in later extending this so that it could be used in the bounds on associated types and with dyn. This syntactic choice is the most scalable in that way.

For RPITIT (i.e. within trait definitions), we defer to implementation work and to stabilization whether to support it or to leave this work to later, e.g. to the point at which we support use<..> in the bounds on associated types.

Note, if we were later to support multiple use<..> items in a list of bounds, that use<T> + use<U> would represent a union, and so use<T> + use<U> would not be equivalent to use<T, U>.

If the bounds of the opaque type reference generics that are not present in the use<..>, that's an error of course, and a machine-applicable fix seems possible and desirable here.

We found the idea of this being a bound surprisingly appealing, and people liked that this choice allows for putting + use<..> at the end of a list of bounds. Doing it this way also means we don't need to migrate the ty macro matcher fragment specifier.

Thanks to @joshtriplett for proposing this appealing option. We resolved a difficult choice between two good options by finding an even better one. That's Rust at its best.

bors added a commit to rust-lang-ci/rust that referenced this issue Jun 18, 2024
Rework `feature(precise_capturing)` to represent `use<...>` as a syntactical bound

Reworks `precise_capturing` for a recent lang-team consensus.

Specifically:

> The conclusion of the team is that we'll make use<..> a bound. That is, we'll support impl use<..> + Trait, impl Trait + use<..>, etc.

> For now, we will support at most one such bound in a list of bounds, and semantically we'll only support these bounds in the item bounds of RPIT-like impl Trait opaque types (i.e., in the places discussed in the RFC).

Lang decision in favor of this approach:

- rust-lang#125836 (comment)

Tracking:

- rust-lang#123432
@tmandry tmandry changed the title Decide for precise capturing: impl use<..> Trait vs use<..> impl Trait Syntax for precise capturing: impl Trait + use<..> Jun 19, 2024
@tmandry
Copy link
Member

tmandry commented Jun 19, 2024

Reopening to formally propose FCP on the above change. I think it is a significant enough delta from the RFC that we would be better off doing an FCP and documenting the rationale now, instead of at stabilization time.

This FCP resolves the unresolved question in the RFC about syntax.

@rfcbot fcp merge

@tmandry tmandry reopened this Jun 19, 2024
@rfcbot
Copy link

rfcbot commented Jun 19, 2024

Team member @tmandry has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Jun 19, 2024
@tmandry tmandry added the I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. label Jun 19, 2024
@pnkfelix
Copy link
Member

I don't know what our official policy is, but @traviscross , would you mind updating the description to reflect the new proposal from #125836 (comment) ?

(Or I guess even just making the line of the description be a link to that comment...)

@rfcbot rfcbot added final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. labels Jun 19, 2024
@rfcbot
Copy link

rfcbot commented Jun 19, 2024

🔔 This is now entering its final comment period, as per the review above. 🔔

@pnkfelix
Copy link
Member

Also, I do not necessarily agree with "Note, if we were later to support multiple use<..> items in a list of bounds, that use + use would represent a union, and so use + use would not be equivalent to use<T, U>." ; but I do not see us as committing to a union (vs intersection) path via our FCP here, so I'm willing to check my box and not make this a formal concern here.

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Jun 19, 2024

@rfcbot reviewed

I concur with @pnkfelix and I specifically want us to leave room for different interpretations of use in the future. There are two that I think make sense, described below. However, thanks to the restrictions of "at most one use" and "must include all generics that appear elsewhere in the bounds", both of these interpretations are equivalent, and I think that's fine for the moment.

To explain the two options, let's start by defining a conceptual bound use_only<G..> that is implemented for all types that reference the generics in G... (but not generics outside that set). That is the right formal mechanism and what e.g. I expect a-mir-formality to think about. Lacking a use_only bound therefore implies any type could be used.

One option then is to desugar use to use_only. This has the counterintuitive implication that use<A> + use<B> is equivalent to use<> which seems surprising.

An alternative option is to have a more complex desugaring where use appears in the surface syntax but not in the internal representation. We would translate a "user-facing" set of bounds as follows. If there is a use<G..> at all, then we take the union G.. of all generics that appear in the bounds anywhere and add use_only<G..>. Otherwise we don't add anything at all. In this way use<A, B> is equivalent to use<A> + use<B>: both would desugar to a single use_only<A, B>.

I think the second rule is more intuitive but also brings up mild concerns. Specifically we try to avoid "discontinuities" like this, where having no uses is "different" than having one or more uses. These kind of rules can have composability problems, but in this instance it might be that it's more composable. Unclear.

Anyway, we don't have to decide this now, but I do want to leave room for it as a possible future direction (which we may never need).

@Dirbaio
Copy link
Contributor

Dirbaio commented Jun 19, 2024

I've got one concern with the + use<> syntax.

If we make use<> a bound, then it makes sense that it only restricts the hidden type further. That's what bounds do after all. Adding a bound can only make types no longer be accepted, it can't make a previously-not-accepted type start being accepted.

In Edition 2024, the default is to capture everything in scope. So, use<> is indeed only restricting. Without use<> the hidden type is allowed to capture anything, with use<> it's only allowed to capture the listed variables. Makes sense.

However, in Edition 2021 and lower, the default is to capture only some things. How does use<> work with this? There's a few options:

  • use<> overrides the default capture list. Therefore it can either remove or add variables to the capture list. Then, it's no longer true that use<> always restricts the hidden type, so it doesn't make sense to add a bound.
  • use<> only further restricts the default capture list. I find this a bit unfortunate because it means you'll still need the + Captures<'a> hack to add a lifetime capture.
  • use<> is entirely disallowed in Edition 2021 and lower. This is also unfortunate, and IIUC the aim is to add new features to all editions if possible, and it feels like in this case it should be.

Neither is particularly great


IMO making use<> a bound feels somewhat wrong, due to:

  • It doesn't make sense in Edition <2021 (see above)
  • Having multiple use<> doing intersection and not union is unintuitive.
  • Artificially disallowing use<> + use<> is also strange, no other bound in Rust behaves like that today.

I think the use<> impl Trait syntax makes much more sense. The way I see it, an opaque type is

  • a list of captures
  • a list of bounds

The list of captures is either automatically inferred (with different rules in <=2021 and >=2024), or explicitly specified with use<>.

Under this mental model, it makes sense for use<> to be a "modifier" applied to the impl keyword, and not a bound. So, use<> impl Trait syntax .The result makes sense for both <=2021 and >=2024 editions, and avoids the weirdness of two use<>s by only allowing one.

@traviscross
Copy link
Contributor Author

traviscross commented Jun 19, 2024

The intuition I'd suggest is that, if a bounds list contains a use<..> bound, that fully specifies the set of captures, and if it doesn't, then the compiler adds one in lowering that lists all of the generics captured under the rules for that edition.

That is, I'd suggest thinking about it in terms of the use<..> bound always being present, conceptually, and that there's a convenient elision rule.

@tmandry
Copy link
Member

tmandry commented Jun 20, 2024

One additional argument that was raised in the meeting, but never really written out as an example, was the use of use<> as a bound on GATs. This really cemented for me the value in thinking of it as a bound. As a quick sketch, the bound could go in the trait:

trait BufferedIterator<A: Arena> {
    type Item<'a>: use<'a>;
    fn next<'a>(&'a mut self, a: &'a mut A) -> Self::Item<'a>;
}

or in use sites:

fn foo(iter: impl BufferedIterator<MyArena, for<'a> Item<'a>: use<'a>>) { ... }

Obviously this is a pretty niche and advanced feature, but it seems to make sense conceptually.

@tmandry
Copy link
Member

tmandry commented Jun 20, 2024

However, in Edition 2021 and lower, the default is to capture only some things. How does use<> work with this? There's a few options:

  • use<> overrides the default capture list. Therefore it can either remove or add variables to the capture list. Then, it's no longer true that use<> always restricts the hidden type, so it doesn't make sense to add a bound.
  • use<> only further restricts the default capture list. I find this a bit unfortunate because it means you'll still need the + Captures<'a> hack to add a lifetime capture.
  • use<> is entirely disallowed in Edition 2021 and lower. This is also unfortunate, and IIUC the aim is to add new features to all editions if possible, and it feels like in this case it should be.

Neither is particularly great

I think this is possibly the strongest argument against use<> as a bound, but I'm personally okay with saying that use<> overrides the default capture list in all editions, therefore it makes slightly less sense to conceptualize it as a bound in earlier editions. This is based on the ideas of joint behavior across editions and that editions are meant to be adopted, so while we prefer to have language features work the same across all editions where possible, we're also okay with only delivering the best possible experience in later editions.

@traviscross traviscross removed the I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. label Jun 26, 2024
flip1995 pushed a commit to flip1995/rust-clippy that referenced this issue Jun 28, 2024
Rework `feature(precise_capturing)` to represent `use<...>` as a syntactical bound

Reworks `precise_capturing` for a recent lang-team consensus.

Specifically:

> The conclusion of the team is that we'll make use<..> a bound. That is, we'll support impl use<..> + Trait, impl Trait + use<..>, etc.

> For now, we will support at most one such bound in a list of bounds, and semantically we'll only support these bounds in the item bounds of RPIT-like impl Trait opaque types (i.e., in the places discussed in the RFC).

Lang decision in favor of this approach:

- rust-lang/rust#125836 (comment)

Tracking:

- rust-lang/rust#123432
@marziply
Copy link

For unions of use<>, as per the example given: use<T> + use<U>, could we not take inspiration from other languages by using the pipe operator? I feel use<T | U> makes more sense.

@rfcbot rfcbot added finished-final-comment-period The final comment period is finished for this PR / Issue. and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Jun 29, 2024
@rfcbot
Copy link

rfcbot commented Jun 29, 2024

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@rfcbot rfcbot added the to-announce Announce this issue on triage meeting label Jun 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-precise_capturing `#![feature(precise_capturing)]` finished-final-comment-period The final comment period is finished for this PR / Issue. T-lang Relevant to the language team, which will review and decide on the PR/issue. to-announce Announce this issue on triage meeting
Projects
None yet
Development

No branches or pull requests

8 participants