Repository navigation
Tracking issue for impl Trait (RFC 1522, RFC 1951, RFC 2071) #34511
Copy link
Copy link
Closed
Labels
B-RFC-implementedBlocker: Approved by a merged RFC and implemented but not stabilized.Blocker: Approved by a merged RFC and implemented but not stabilized.B-unstableBlocker: Implemented in the nightly compiler and unstable.Blocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCT-langRelevant to the language teamRelevant to the language teamdisposition-mergeThis issue / PR is in PFCP or FCP with a disposition to merge it.This issue / PR is in PFCP or FCP with a disposition to merge it.finished-final-comment-periodThe final comment period is finished for this PR / Issue.The final comment period is finished for this PR / Issue.
Description
NEW TRACKING ISSUE = #63066
Implementation status
The basic feature as specified in RFC 1522 is implemented, however there have been revisions that are still in need of work:
- impl Trait in argument position
- extend to cover traits
- support elision Argument-position
impl Traitrequires a named lifetime #49287
- impl Trait in return position is basically implemented, but:
- filter lifetimes
- code accepts types with out-of-scope lifetimes 🚀 existential impl Trait allows lifetimes that would not be allowed by abstract type #46541
- ICEs when interacting with elision
impl TraitLifetime Elision #43396 - ICE combining universal and existential ICE combining universal plus existential impl Trait #46685
- ICE with nested lifetimes Nested "impl trait" with lifetimes causing internal compiler error #46464
- Infinitely recursive types not detected Infinite size checker doesn't see through impl Trait, leading to rustc overflow #38064
- Better error message for infinitely recursive types Improve error message for infinitely recursive impl Trait type #47659 (fixed in Forbid recursive impl trait #56074)
- do not accept nested impl trait described here (fixed in Fix nested impl trait lifetimes #48072)
-
let x: impl Trait -
staticandconst T: impl Trait -
abstract type- preliminary refactoring possibility
- in modules
- in traits
RFCs
There have been a number of RFCs regarding impl trait, all of which are tracked by this central tracking issue.
- Minimal
impl Traitrfcs#1522- the original, which covered only impl Trait in return position for inherent functions
- RFC: Finalize syntax and parameter scoping for
impl Trait, while expanding it to arguments rfcs#1951- settling on a particular syntax design, resolving questions around the some/any proposal and others.
- resolving questions around which type and lifetime parameters are considered in scope for an impl Trait.
- adding impl Trait to argument position.
- Named existentials and impl Trait variable declarations rfcs#2071
- named
abstract typein modules and impls- Finalize syntax (Permit impl Trait in type aliases rfcs#2515)
- Update RFC RFC: Associated type bounds of form
MyTrait<AssociatedType: Bounds>rfcs#2289 to match that syntax if that RFC gets merged.
- use of impl trait in
let,const, andstaticpositions
- named
- mini-RFC: Finalize syntax of
impl Traitanddyn Traitwith multiple bounds rfcs#2250- Finalizing the syntax of
impl Traitanddyn Traitwith multiple bounds
- Finalizing the syntax of
Unresolved questions
The implementation has raised a number of interesting questions as well:
- What is the precedence of the
implkeyword when parsing types? Discussion: 1- e.g., how to associate
Sendforwhere F: Fn() -> impl Foo + Send? - Resolved by mini-RFC: Finalize syntax of
impl Traitanddyn Traitwith multiple bounds rfcs#2250. - Implemented (?) in syntax: Lower priority of
+inimpl Trait/dyn Trait#45294
- e.g., how to associate
- Should we allow
impl Traitafter->infntypes or parentheses sugar? [impl Trait] Should we allowimpl Traitafter->infntypes or parentheses sugar? #45994 - Do we have to impose a DAG across all functions to allow for auto-safe leakage, or can we use some kind of deferral. Discussion: 1
- Present semantics: DAG.
- How should we integrate impl trait into regionck? Discussion: 1, 2
- Resolved in impl Trait Lifetime Handling #45701
- Should we permit specifying types if some parameters are implicit and some are explicit? e.g.,
fn foo<T>(x: impl Iterator<Item = T>>)?- Current behavior: An error to specify types
- Other alternatives: treat
impl Traitas arguments in the list, permitting migration
- Some concerns about nested impl Trait usage
- Should the syntax in an impl be
existential type Foo: Barortype Foo = impl Bar? (see here for discussion)- Settled by Permit impl Trait in type aliases rfcs#2515.
- Should the set of "defining uses" for an
existential typein an impl be just items of the impl, or include nested items within the impl functions etc? (see here for example)
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
B-RFC-implementedBlocker: Approved by a merged RFC and implemented but not stabilized.Blocker: Approved by a merged RFC and implemented but not stabilized.B-unstableBlocker: Implemented in the nightly compiler and unstable.Blocker: Implemented in the nightly compiler and unstable.C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCT-langRelevant to the language teamRelevant to the language teamdisposition-mergeThis issue / PR is in PFCP or FCP with a disposition to merge it.This issue / PR is in PFCP or FCP with a disposition to merge it.finished-final-comment-periodThe final comment period is finished for this PR / Issue.The final comment period is finished for this PR / Issue.
Activity
pthariensflame commentedon Jul 15, 2016
@aturon Can we actually put the RFC in the repository? (@mbrubeck commented there that this was a problem.)
aturon commentedon Jul 15, 2016
Done.
eddyb commentedon Jul 31, 2016
First attempt at implementation is #35091 (second, if you count my branch from last year).
One problem I ran into is with lifetimes. Type inference likes to put region variables everywhere and without any region-checking changes, those variables don't infer to anything other than local scopes.
However, the concrete type must be exportable, so I restricted it to
'staticand explicitly named early-bound lifetime parameters, but it's never any of those if any function is involved - even a string literal doesn't infer to'static, it's pretty much completely useless.One thing I thought of, that would have 0 impact on region-checking itself, is to erase lifetimes:
impl Traitshould care about lifetimes - a quick search forReveal::Allsuggests that's already the case in the compilerimpl Traitin the return type of a function, that it outlives the call of that function - this means that any lifetime is, by necessity, either'staticor one of the lifetime parameters of the function - even if we can't know which (e.g. "shortest of'aand'b")impl Trait(i.e. on all lifetime parameters in scope, same as with type parameters): invariance is easiest and gives more control to the callee, while contravariance lets the caller do more and would require checking that every lifetime in the return type is in a contravariant position (same with covariant type parametrism instead of invariant)X<'a, 'a>orX<'static>), which must be detected and errored on, as they can't be proven for those lifetimesThat last point about auto trait leakage is my only worry, everything else seems straight-forward.
It's not entirely clear at this point how much of region-checking we can reuse as-is. Hopefully all.
cc @rust-lang/lang
arielb1 commentedon Jul 31, 2016
@eddyb
But lifetimes are important with
impl Trait- e.g.I mentioned that several times in the RFC threads
arielb1 commentedon Jul 31, 2016
trait-object-less version:
This is either UB or not depending on the definition of
as_debug.eddyb commentedon Jul 31, 2016
@arielb1 Ah, right, I forgot that one of the reasons I did what I did was to only capture lifetime parameters, not anonymous late-bound ones, except it doesn't really work.
eddyb commentedon Jul 31, 2016
@arielb1 Do we have a strict outlives relation we can put between lifetimes found in the concrete type pre-erasure and late-bound lifetimes in the signature? Otherwise, it might not be a bad idea to just look at lifetime relationships and insta-fail any direct or indirect
'a outlives 'bwhere'ais anything other than'staticor a lifetime parameter and'bappears in the concrete type of animpl Trait.nikomatsakis commentedon Aug 3, 2016
Sorry for taking a while to write back here. So I've been thinking
about this problem. My feeling is that we do, ultimately, have to (and
want to) extend regionck with a new kind of constraint -- I'll call it
an
\inconstraint, because it allows you to say something like'0 \in {'a, 'b, 'c}, meaning that the region used for'0must beeither
'a,'b, or'c. I'm not sure of the best way to integratethis into solving itself -- certainly if the
\inset is a singletonset, it's just an equate relation (which we don't currently have as a
first-class thing, but which can be composed out of two bounds), but
otherwise it makes things complicated.
This all relates to my desire to make the set of region constraints
more expressive than what we have today. Certainly one could compose a
\inconstraint out ofORand==constraints. But of course moreexpressive constraints are harder to solve and
\inis no different.Anyway, let me just lay out a bit of my thinking here. Let's work with this
example:
I think the most accurate desugaring for a
impl Traitis probably anew type:
Now the
impl Iterator<Item=u32>infooshould behave the same asFooReturn<'a,'b>would behave. It's not a perfect match though. Onedifference, for example, is variance, as eddyb brought up -- I am
assuming we will make
impl Foo-like types invariant over the typeparameters of
foo. The auto trait behavior works out, however.(Another area where the match might not be ideal is if we ever add the
ability to "pierce" the
impl Iteratorabstraction, so that code"inside" the abstraction knows the precise type -- then it would sort
of have an implicit "unwrap" operation taking place.)
In some ways a better match is to consider a kind of synthetic trait:
Now we could consider the
impl Iteratortype to be like<() as FooReturn<'a,'b>>::Type. This is also not a perfect match, because wewould ordinarily normalize it away. You might imagine using specialization
to prevent that though:
In this case,
<() as FooReturn<'a,'b>>::Typewould not normalize,and we have a much closer match. The variance, in particular, behaves
right; if we ever wanted to have some type that are "inside" the
abstraction, they would be the same but they are allowed to
normalize. However, there is a catch: the auto trait stuff doesn't
quite work. (We may want to consider harmonizing things here,
actually.)
Anyway, my point in exploring these potential desugarings is not to
suggest that we implement "impl Trait" as an actual desugaring
(though it might be nice...) but to give an intuition for our job. I
think that the second desugaring -- in terms of projections -- is a
pretty helpful one for guiding us forward.
One place that this projection desugaring is a really useful guide is
the "outlives" relation. If we wanted to check whether
<() as FooReturn<'a,'b>>::Type: 'x, RFC 1214 tells us that we can prove thisso long as
'a: 'xand'b: 'xholds. This is I think how we wantto handle things for impl trait as well.
At trans time, and for auto-traits, we will have to know what
XXXis, of course. The basic idea here, I assume, is to create a type
variable for
XXXand check that the actual values which are returnedcan all be unified with
XXX. That type variable should, in theory,tell us our answer. But of course the problem is that this type
variable may refer to a lot of regions which are not in scope in the
fn signature -- e.g., the regions of the fn body. (This same problem
does not occur with types; even though, technically, you could put
e.g. a struct declaration in the fn body and it would be unnameable,
that's a kind of artificial restriction -- one could just as well move
the struct outside the fn.)
If you look both at the struct desugaring or the impl, there is an
(implicit in the lexical structure of Rust) restriction that
XXXcanonly name either
'staticor lifetimes like'aand'b, whichappear in the function signature. That is the thing we are not
modeling here. I'm not sure the best way to do it -- some type
inference schemes have a more direct representation of scoping, and
I've always wanted to add that to Rust, to help us with closures. But
let's think about smaller deltas first I guess.
This is where the
\inconstraint comes from. One can imagine addinga type-check rule that (basically)
FR(XXX) \subset {'a, 'b}--meaning that the "free regions" appearing in XXX can only be
'aand'b. This would wind up translating to\inrequirements for thevarious regions that appear in
XXX.Let's look at an actual example:
Here, the type if
conditionis true would be something likeCloned<SliceIter<'a, i32>>. But ifconditionis false, we wouldwant
Cloned<SliceIter<'b, i32>>. Of course in both cases we wouldwind up with something like (using numbers for type/region variables):
If we then instantiate the variable 0 to
Cloned<SliceIter<'2, i32>>,we have
'0: '2and'1: '2, or a total set of region relationslike:
So what value should we use for
'2? We have also the additionalconstraint that
'2 in {'a, 'b}. With the fn as written, I think wewould have to report an error, since neither
'anor'bis acorrect choice. Interestingly, though, if we added the constraint
'a: 'b, then there would be a correct value ('b).Note that if we just run the normal algorithm, we would wind up with
'2being'body. I'm not sure how to handle the\inrelationsexcept for exhaustive search (though I can imagine some special
cases).
OK, that's as far as I've gotten. =)
nikomatsakis commentedon Aug 10, 2016
On the PR #35091, @arielb1 wrote:
I thought it would make more sense to discuss here. @arielb1, can you elaborate more on what you have in mind? In terms of the analogies I made above, I guess you are fundamentally talking about "pruning" the set of lifetimes that would appear either as parameters on the newtype or in the projection (i.e.,
<() as FooReturn<'a>>::Typeinstead of<() as FooReturn<'a,'b>>::Typeor something?I don't think that the lifetime elision rules as they exist would be a good guide in this respect: if we just picked the lifetime of
&selfto include only, then we wouldn't necessarily be able to include the type parameters from theSelfstruct, nor type parameters from the method, since they may have WF conditions that require us to name some of the other lifetimes.Anyway, it'd be great to see some examples that illustrate the rules you have in mind, and perhaps any advantages thereof. :) (Also, I guess we would need some syntax to override the choice.) All other things being equal, if we can avoid having to pick from N lifetimes, I'd prefer that.
Auto merge of #35091 - eddyb:impl-trait, r=nikomatsakis
petrochenkov commentedon Aug 12, 2016
I haven't seen interactions of
impl Traitwith privacy discussed anywhere.Now
fn f() -> impl Traitcan return a private typeS: Traitsimilarly to trait objectsfn f() -> Box<Trait>. I.e. objects of private types can walk freely outside of their module in anonymized form.This seems reasonable and desirable - the type itself is an implementation detail, only its interface, available through a public trait
Traitis public.However there's one difference between trait objects and
impl Trait. With trait objects alone all trait methods of private types can get internal linkage, they will still be callable through function pointers. Withimpl Traits trait methods of private types are directly callable from other translation units. The algorithm doing "internalization" of symbols will have to try harder to internalize methods only for types not anonymized withimpl Trait, or to be very pessimistic.arielb1 commentedon Aug 14, 2016
@nikomatsakis
The "explicit" way to write
foowould beHere there is no question about the lifetime bound. Obviously, having to write the lifetime bound each time would be quite repetitive. However, the way we deal with that kind of repetition is generally through lifetime elision. In the case of
foo, elision would fail and force the programmer to explicitly specify lifetimes.I am opposed to adding explicitness-sensitive lifetime elision as @eddyb did only in the specific case of
impl Traitand not otherwise.nikomatsakis commentedon Aug 15, 2016
@arielb1 hmm, I'm not 100% sure how to think about this proposed syntax in terms of the "desugarings" that I discussed. It allows you to specify what appears to be a lifetime bound, but the thing we are trying to infer is mostly what lifetimes appear in the hidden type. Does this suggest that at most one lifetime could be "hidden" (and that it would have to be specified exactly?)
It seems like it's not always the case that a "single lifetime parameter" suffices:
In this case, the hidden iterator type refers to both
'aand'b(although it is variant in both of them; but I guess we could come up with an example that is invariant).430 remaining items
robbym commentedon Jun 20, 2019
Will something like the following be possible?
RustyYato commentedon Jun 20, 2019
You can do this now, although only with a
hintfunction to specify the concrete type ofInterfaceCryZe commentedon Jun 20, 2019
How would you write it if the callback would be able to choose its argument type? Actually nvm, I guess you could solve that one via a normal generic.
Ekleog commentedon Jun 20, 2019
@CryZe What you're looking for is unrelated to
impl Trait. See rust-lang/rfcs#2413 for all I know about it.It would potentially look something like that :
robbym commentedon Jun 20, 2019
@KrishnaSannasi Ah, interesting. Thanks!
jethrogb commentedon Jul 15, 2019
Is this supposed to work?
DoumanAsh commentedon Jul 25, 2019
Do we have to keep
existentialkeyword in language forexistential_typefeature?cramertj commentedon Jul 25, 2019
@jethrogb Yes. The fact that it currently doesn't is a bug.
jethrogb commentedon Jul 25, 2019
@cramertj Ok. Should I file a separate issue for that or is my post here enough?
cramertj commentedon Jul 25, 2019
Filing an issue would be great, thanks! :)
alexreg commentedon Jul 26, 2019
I think the intention is to immediately deprecate this when the type-alias-impl-trait feature is implemented (i.e., put in a lint) and eventually remove it from the syntax.
Someone can maybe clarify though.
Centril commentedon Jul 28, 2019
Closing this in favor of a meta-issue which tracks
impl Traitmore generally: #63066TitanThinktank commentedon Aug 19, 2019