or
or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up
Syntax | Example | Reference | |
---|---|---|---|
# Header | Header | 基本排版 | |
- Unordered List |
|
||
1. Ordered List |
|
||
- [ ] Todo List |
|
||
> Blockquote | Blockquote |
||
**Bold font** | Bold font | ||
*Italics font* | Italics font | ||
~~Strikethrough~~ | |||
19^th^ | 19th | ||
H~2~O | H2O | ||
++Inserted text++ | Inserted text | ||
==Marked text== | Marked text | ||
[link text](https:// "title") | Link | ||
 | Image | ||
`Code` | Code |
在筆記中貼入程式碼 | |
```javascript var i = 0; ``` |
|
||
:smile: | ![]() |
Emoji list | |
{%youtube youtube_id %} | Externals | ||
$L^aT_eX$ | LaTeX | ||
:::info This is a alert area. ::: |
This is a alert area. |
On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?
Please give us some advice and help us improve HackMD.
Syncing
xxxxxxxxxx
Introduction
This document lays out 5 motivating examples for combinations of RPITIT hidden types that we may want to support, and 5 different sets of capture rules.
Background facts
Refresher on the
Captures
trickConsider the following trait definition.
Playground link
We're expecting that the return type of
foo
will contain the lifetimes's
and't
. If we want to express that explicitly as a bound onAssoc
, how would we do that? We might think to write:Playground link
But that is wrong. If
Assoc
is to contain the lifetimes's
and't
, then those lifetimes must outliveAssoc
, not the other way around. What we really want to say is this:But that's not legal in Rust today. So what we say instead is:
Playground link
This is called the
Captures
trick. It asserts that the lifetimes must outlive the type parameter, which is the semantic that we want. It may be applied to generic parameters, associated types, GATs, and to the opaque types in TAIT, ATPIT, and RPITIT.Generic type parameter lifetime rules (no bounds required in the trait)
The impl of a trait for a type can substitute, for any generic parameter of the trait, a type that includes any lifetime that is in scope (within the impl) without an explicit bound on the generic parameter in the trait definition.
Playground link
Note that explicit bounds on generic parameters do not risk overcapturing. For example:
Playground link
That is, if an impl substitutes a type for the generic parameter that does not use a lifetime present in the bounds, callers of thy type's trait methods can observe and rely on that.
Associated type / GAT lifetime rules (no bounds required in the trait)
The impl of a trait for a type can substitute, for any GAT of the trait, a type that includes any lifetime that is in scope (within the impl) without an explicit bound on the GAT in the trait definition.
Playground link
Note that explicit bounds on GATs do not risk overcapturing. For example:
Playground link
That is, if an impl substitutes a type for the GAT that does not use a lifetime present in the bounds, users of the GAT can observe and rely on that.
Other examples
Show
Other examples
Playground link
In the above example, the lifetime is a parameter to the trait. In the impl, it is included in the type substituted for the associated type but it is not included in the
Self
type.Playground link
In the above example, the trait itself does not have a lifetime parameter. However, the
Self
type in the impl does contain a lifetime parameter, and that lifetime is included in the type substituted for the associated type.RPIT lifetime capture rules (bounds required on the opaque type)
The hidden type in RPIT can only capture a lifetime if that lifetime appears explicitly in the bounds of the opaque type.
RPIT on inherent methods
On inherent methods, lifetimes in scope from the impl and lifetimes in scope from the method signature must both be stated explicitly in the bounds of the RPIT opaque type if they are to be captured by the hidden type.
Playground link
Note that for RPIT, the bound on the opaque type does not just say what the hidden type may capture but it also says what the opaque type does capture. If the opaque type captures less than the bound allows, callers cannot rely on that.
RPIT on free functions
On free functions, lifetimes in scope from the function signature must be stated explicitly in the bounds of the RPIT opaque type if they are to be captured by the hidden type.
Playground link
TAIT lifetime capture rules (bounds required on opaque type)
The current implementation of TAIT exactly matches the lifetime capture rules of RPIT. Explicit lifetime bounds are required on the opaque type for the lifetime to be captured by the hidden type.
Playground link
Note that for TAIT, like RPIT, the bound on the opaque type does not just say what the hidden type may capture but it also says what the opaque type does capture. If the opaque type captures less than the bound allows, callers cannot rely on that.
ATPIT lifetime capture rules (bounds required on opaque type but not in trait)
The current implementation of ATPIT follows the existing lifetime rules of both RPIT and GATs. In the trait definition, no bounds are required on the GAT. In the impl, lifetimes in scope from the impl must be stated explicitly in the bounds of the ATPIT opaque type if they are to be captured by the hidden type.
Playground link
Note that for ATPIT, like RPIT, the bound on the opaque type does not just say what the hidden type may capture but it also says what the opaque type does capture. If the opaque type captures less than the bound allows, callers cannot rely on that.
Summary of background
Generic parameters, associated types, and GATs defined in traits may be substituted in impls with types that contain lifetimes that were not mentioned in the bound of the generic parameter or the GAT in the trait definition. If a generic parameter or GAT is bounded by a lifetime in the trait definition but the impl for a particular type does not use the lifetime in the type substituted for the generic parameter or GAT, then users of the impl for that type may observe and rely on that "refinement".
For an RPIT hidden type to capture a lifetime, that lifetime must appear in the bounds of the opaque type. If a lifetime does appear in the bounds of the opaque type, then even if the hidden type does not in fact use that lifetime, callers cannot rely on that; the opaque type is still bounded by the lifetime.
TAIT, as implemented and as proposed for stabilization, follows the current stable behavior of RPIT exactly.
ATPIT, as implemented and as proposed for stabilization, follows both the current stable behaviors of RPIT and GATs exactly.
For multiple lifetimes, particularly when those lifetimes are invariant, the
Captures
trick is required when dealing with generic parameters in traits, associated types, GATs, RPIT, TAIT, and ATPIT.Motivating examples
Example A: Capturing a lifetime from the Self type
Example A.1
Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example A.2
Or equivalently (on nightly), using an RPIT in the impl[1]:
Playground link
Important to note here and below that we're only concerned with the capture rules of the RPITIT. Not (at least until examples D and E) the capture rules of the totally separate RPIT in the impl.
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example A.3
Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Matrix
How each rule would affect this example:
Example B: Capturing a non-Self trait input lifetime
Example B.1
Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example B.2
Or equivalently, ignoring
#[refine]
rules which are not relevant for the RPITIT inference algorithm:Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example B.3
Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Matrix
How each rule would affect this example:
Example C: Capturing a method lifetime
Example C.1
This example uses a late-bound lifetime, but I strongly believe that we should not be treating early- and late-bound lifetimes differently in RPITIT capture rules.
Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example C.2
Or equivalently, ignoring
#[refine]
rules which are not relevant for the RPITIT inference algorithm:Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example C.3
Playground link
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Matrix
How each rule would affect this example:
Example D: Capturing a lifetime from the Self type (alt)
Example D.1
This example explores one of the problems that was discussed during the "RPITIT stabilization ahoy" meeting, where inconsistencies between RPITIT and RPIT lifetime capture rules means that you can't just "copy and paste"
-> impl Sized
from the trait to the impl in all cases.Playground link
Note that the trait in this example is identical to the trait in Example A. Only the
impl
is different.Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example D.2
Playground link
Note that this is related to Example A.
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Matrix
How each rule would affect this example:
Example E: Capturing a non-Self trait input lifetime (alt)
Example E.1
Playground link
Note that the trait in this example is identical to the trait in Example B. Only the
impl
is different.Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example E.2
Playground link
Note that this is related to Example B.
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Matrix
How each rule would affect this example:
Example F: Capturing a method lifetime (alt)
Example F.1
Playground link
Note that the trait in this example is identical to the trait in Example C. Only the
impl
is different.Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Example F.2
Playground link
Note that this is related to Example C.
Desugaring
Show
We can view it as roughly desugaring to:
Playground link
Matrix
How each rule would affect this example:
Matrix of possibilities
"Strict"
This is strict adherence to the RPIT capture rules. This allows no lifetimes to be captured.
Advantages:
Drawbacks:
Changes to be made to the compiler:
Need to modify rpitit lifetime mapping to deny all impl header lifetimes unless captured in bounds, which isn't difficult.
"Current"
This is the current behavior implemented for RPITITs on master as of #113182. It allows an impl's RPITIT type to capture any lifetimes mentioned in the impl header, both from the self type and from the trait generic.
Advantages:
Drawbacks:
-> impl Trait
in an impl still needs to explicitly say that it captures all of the lifetimes in its hidden type.Changes to be made to the compiler:
"Only Self"
Modification to the above strategy to only allow capturing lifetimes that are mentioned directly in the impl's self type.
From last meeting, it seems we're not exactly keen of this solution, so I won't flesh it out too much.
Modifications to the compiler to enable this are not difficult, though.
"Any in scope"
This allows users to write impls that capture lifetimes from the method inputs as well, as demonstrated by example "C".
Advantages:
Disadvantages:
Changes to the compiler:
"Current + Copyable signature"
"copyable signature" is what I call the property of being able to copy everything after the
->
in the trait directly into the impl without having to add extra lifetimes.Advantages:
+ Captures<'self_lifetime>
required in code like example "A".Disadvantages:
#[refine]
, and we don't have a syntax for that other than concrete types or+ 'static
. Implication: Pushing more people toward explicit GATs, ATPIT.Changes to the compiler:
"Any in scope + Copyable signature"
Advantages:
Disadvantages:
Changes to the compiler:
Feedback
tmandry's opinion
Given that we rule out being consistent with inherent impls, there's a 2x2 matrix being decided right now: Copyable signature or no, Any in scope or no.
Argument for "copyable signature": It's much easier to explain and prevents people from depending on particular behavior that you didn't know you were opting into when you copied the signature.[2] Plus, we don't think we can give you a serviceable error message if you copy the signature, which is important to consider.
We should go with "Current + Copyable signature", and consider the "Any in scope" separately as an edition question. Capture rules for generic lifetimes on TAIT/ITIAT items should be consistent with the "Any in scope" question.
ITIAT should be changed to allow capturing lifetimes in the parent scope, for consistency with the "Current" behavior. Users who do not want to capture any implicit lifetimes should use an associated type, or
#[refine]
, set to a TAIT outside the trait impl.There's later decisions about changing the behavior of inherent impls, and (as I said before) changing the "any in scope" question.
TC summary
Here's how I would summarize things:
If we view the RPITIT as similar to an associated type / GAT, then "any in scope" seems consistent with current stable behavior and the obvious desugaring. It seems conservative in that sense.
In the presence of refinement, "any in scope" does not lead to overcapturing issues that must be resolved through desugaring (because it already essentially works like GATs/ATs).
"Copyable signature" does raise overcapturing issues that users would need to address by manual desugaring to TAITs.
Because "copyable signature" affects RPIT in the impl, that raises additional consistency questions.
Adopting "copyable signature" probably commits us to a direction where RPIT (over an edition), TAIT, and ATPIT would automatically capture all in-scope lifetime parameters (the "big question").
If that is or may be what we want to do, we need to answer the question of whether we should make RPITIT (RPIT in the trait impl), TAIT, and ATPIT work that way in this edition and accept the inconsistency with RPIT, or whether we should make RPITIT (RPIT in the trait impl), TAIT, and ATPIT work like RPIT does now for this edition and fix all of them together over an edition.
If we wish for RPITIT to move quickly, we either need to answer that "big question" quickly or we need to decide quickly to punt the question to an edition and go with "any in scope" (without "copyable signature").
This is ignoring
#[refine]
s rules right now, because they are an additional check and have no influence on the RPITIT type inference algorithm. ↩︎Specifically, you as an impl author opted into not capturing any lifetimes from the impl when you failed to write
+ Captures<'a>
for those lifetimes. (In "Original" these would be allowed by the trait even though they are not made explicit.) Users of your impl are then allowed to rely on you not capturing those lifetimes, though you could have captured them if you wrote the Captures bound. This seems extremely surprising. ↩︎