--- title: ITE meeting minutes tags: minutes, impl-trait-everywhere date: 2023-06-29 - present url: https://hackmd.io/UA-GDUiiRD27Uxh8QBuBbQ --- Project board: https://github.com/orgs/rust-lang/projects/34 # 2023-09-07 Attendees: nikomatsakis, tmandry, CE, TC Minutes: TC In this meeting we worked on the ["RPITIT/AFIT stabilization report"](https://hackmd.io/z8in5DLRT4StYM9Y77yfrg). # 2023-08-31 Stabilization report will need to cover * Confidence in the implementation * Why stabilizing this without RTN, AFIT * Capture rules * Any other decisions we made :) Prior art * [Return Position Impl Trait in Trait Stabilization](/ZEnb67s3RkysfNK2-tBMIw) (lang team meeting) * Michael has a pretty comprehensive doc he made for the types team deep dive. * [GATs stabilization report](https://github.com/rust-lang/rust/pull/96709) Remainder of minutes for this meeting are [here](https://hackmd.io/f8YtneApSqiokei0oMfkPQ). # 2023-08-24 Minutes are [here](https://hackmd.io/bgaakRUITh-Akk1DxWJlgQ). # 2023-08-03 Minutes: TC There are two blocking issues... ## "RPITIT is allowed to name any in-scope lifetime parameter, unlike inherent RPIT methods" https://github.com/rust-lang/rust/issues/112194 This can probably be resolved based on the design meeting. ## Implement refinement check for RPITITs https://github.com/rust-lang/rust/pull/111931 CE: Not sure I can land the PRs needed for this. Not sure t-types will let me. The impact on the compiler is too bad. And I'm not sure this is something that we need in the end. TC: There are open spots on the t-lang design meeting calendar. T-lang is certainly receptive to implementation concerns. It seems we should put together a document that explains the problem and see whether this affects whether t-lang is willing to walk through this one-way door. (Discussion about that...) CE: With the type system that Rust implements, this check isn't something that makes a lot of sense. It's fighting what our type system does. CE: I'm more of less confident that it's not possible to implement the RFC as it was specified. The types team was not involved in the RFC and may not have existed yet. CE: In order to do refining we have to choose an implementation that refines the trait signature. In order to choose an implemenation for a trait signature we have to know type information about the program. In order to know the type information, we have to do inferenece. To do that, we need the function signature... tmandry: So, is adding the check hard? CE: Yes, the check is hard. It requires adding a mode that requires us to hide RPITITs in a way that's a big hack. CE: The problem is that we're conflating two kinds of refinements. There's "GATs are always refining", and there's the other one. tmandry: We shouldn't let you write a function signature in the impl that's different than the trait if you can't take advantage of it. CE: You're aware of my concerns with that first part? Right now we allow signatures that seem refining but we don't allow people to rely on that. tmandry: I'm aware. Maybe we want to soften specifically on the case of elided lifetimes if it's possible. CE: So then the argument is... "Rust only cares if you write impls that are more specific than the trait. Why does Rust care if you write one more general than the trait?" tmandry: There's a forward-compat issue. We maybe can't do this without an edition or some other mechanism. CE: I'm not sure we can take advantage of refinement ever regardless of the refinement check. tmandry: Maybe we can separate RPITITs from the more general question? How costly is the specific check for RPITIT? CE: It's what implemented in this PR. But this is what I don't think is going to be allowed in terms of adding technical debt. The problem is that I agree. tmandry: Is there a better way to implement it? CE: No. I implemented RPITIT in the beginning as always refining. (other discussion...) CE: ALso have other work to do, such as having RPTITIT capture all lifetimes. TC: How much of this work is shared with TAIT/ATPIT/RPIT. CE: Essentially all of it. CE: The way that `async fn`s capture their lifetimes currently is a bit of a hack. Action items: The meeting consensus was that we need to write up a document on the problem and the options for moving forward. This will be in the form of a design document and we'll propose it for a t-lang design meeting. TC will drive this document after finishing the RFC for the 2024 captures rules. He'll query CE for the needed details, and all of us, TM, CE, TC, will collaborate on it. We hope the process of writing this out will provide a similar clarity that the process of writing out the captures situation provided. # July 6, 2023 ```rust #![feature(return_position_impl_trait_in_trait)] trait Foo { fn foo(self) -> impl Sized; } impl Foo for &() { fn foo(self) -> impl Sized { //~^ ERROR hidden type for `impl Sized` captures lifetime that does not appear in bounds self } } ``` ```rust #![feature(return_position_impl_trait_in_trait)] trait Foo { fn foo(self) -> impl Sized; } impl<'a> Foo for &'a () { fn foo(self) -> impl Sized + 'a { self } } ``` Forms of consistency: * inherent impl : trait impl :heavy_check_mark: * neither inherent impl capture `'a` above * inherent : trait :x: * trait impl captures `'a` above * but inherent impl does not * `-> impl Trait` to Generic Associated Types * GATs always capture (at trait level) all trait inputs * * current capture : future capture * Niko's opinion is that we be capturing more things in future # June 29, 2023 ### 1. RPITITs can capture any in-scope early-bound lifetimes: * https://github.com/rust-lang/rust/issues/112194 * https://github.com/rust-lang/rust/issues/109016 RPITITs can capture early-bound lifetimes not mentioned in the opaque: ```rust #![feature(return_position_impl_trait_in_trait)] trait Early { fn f<'early: 'early>(&'early self) -> impl Sized; } impl Early for () { fn f<'early: 'early>(&'early self) -> impl Sized + 'early { self } } ``` ...but not late-bound lifetimes: ```rust #![feature(return_position_impl_trait_in_trait)] trait Late { fn f<'late>(&'late self) -> impl Sized; } impl Late for () { fn f<'late>(&'late self) -> impl Sized + 'late { self } } ``` ``` error: `impl` item signature doesn't match `trait` item signature --> src/lib.rs:8:5 | 4 | fn f<'late>(&'late self) -> impl Sized; | --------------------------------------- expected `fn(&'late ()) -> impl Sized + '1` ... 8 | fn f<'late>(&'late self) -> impl Sized + 'late { self } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 ()) -> impl Sized + '1` | = note: expected signature `fn(&'late ()) -> impl Sized + '1` found signature `fn(&'1 ()) -> impl Sized + '1` = help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait` = help: verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output ``` Concerns: * I would consider the early-bound example a sort of refinement, since we can take advantage of the fact that we're outliving `'early` in the implementation. * Even if the early-bound example is *okay* and we want that behavior, it seems inconsistent that it doesn't work for late-bound lifetimes Late bound lifetimes are specifically annoying, because we currently have no way of fixing them currently without synthesizing a new early-bound lifetime on the GAT. * GAT substs are currently all function substs (early-bound lts) + late bound lifetimes captured by the opaque (and duplicated early-bound lifetimes) ##### Notes: * We want to be able to capture lifetimes on the impl, incl self. How to fix it: 1. Explicitly state that you want to capture self type's lifetimes * What about lifetimes in the impl header that aren't in the self, like trait substs? 3. Implicitly capture `Self` always * Similarly, do we only want to implicitly capture `Self`'s lifetimes? ##### Inconsistency with inherent impl ```rust= struct Foo<'a>(&'a ()); impl<'a> Foo<'a> { fn foo(&self) -> impl Sized { self } } ``` ```text= error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds --> src/lib.rs:4:35 | 4 | fn foo(&self) -> impl Sized { self } | ----- ---------- ^^^^ | | | | | opaque type defined here | hidden type `&Foo<'a>` captures the anonymous lifetime defined here | help: to declare that `impl Sized` captures `'_`, you can add an explicit `'_` lifetime bound | 4 | fn foo(&self) -> impl Sized + '_ { self } | ++++ ``` ### 2. Should nested opaques inherit their parent opaque's generics * https://github.com/rust-lang/rust/issues/108580#issuecomment-1448993698 How should this desugar? ```rust #![feature(return_position_impl_trait_in_trait)] trait Foo { fn bar(&self) -> impl Iterator<Item = impl Sized> + '_; } impl Foo for () { fn bar(&self) -> impl Iterator + '_ { vec![()].into_iter() } } ``` ```rust #![feature(return_position_impl_trait_in_trait)] trait Foo { type Outer<'a>: Iterator<Item = Self::Inner> + 'a; type Inner: Sized; fn bar(&self) -> Self::Outer<'_>; } impl Foo for () { // These are the values inferred by the RPITIT's hidden type inference algorithm type Outer<'a> = impl Iterator + 'a; type Inner = <Self::Outer<'???> as Iterator>::Item; fn bar(&self) -> Self::Outer<'_> { vec![()].into_iter() } } ``` ```rust #![feature(return_position_impl_trait_in_trait)] trait Foo { type Outer<'a>: Iterator<Item = Self::Inner<'a>> + 'a; type Inner<'bivariant>: Sized; fn bar(&self) -> Self::Outer<'_>; } impl Foo for () { // These are the values inferred by the RPITIT's hidden type inference algorithm type Outer<'a> = impl Iterator + 'a; type Inner<'bivariant> = <Self::Outer<'bivariant> as Iterator>::Item; fn bar(&self) -> Self::Outer<'_> { vec![()].into_iter() } } ``` The "solution" is to make nested opaques inherit the generics from their parents. This means that all the late-bound lifetimes captured by their parents are nameable.