-
Notifications
You must be signed in to change notification settings - Fork 530
Document type alias impl trait #1317
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
base: master
Are you sure you want to change the base?
Conversation
} | ||
``` | ||
|
||
When such a type alias is used as the return type of multiple functions, all functions must use the same hidden type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be good to start this section with something that defines "hidden type" as the real type behind a TAIT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... RPITs have the same thing, so I could bring it up where "abstract types" are explained?
src/types/impl-trait.md
Outdated
|
||
Note that this is very different from using `impl Trait` in argument position, as there is no anonymous generic parameter introduced. | ||
|
||
Binding a hidden type works in both directions, not just assigning a hidden type value to the opaque type, but also reading an opaque type into a hidden type value: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "Binding a hidden type" is confusing to me here. Can we rephrase to avoid "binding"? Or explain what we mean by that? Perhaps something like: "The hidden type can be defined by any usage of it. This includes returning a concrete type from a function with the opaque type and assigning a value with the opaque type to a variable with a concrete type."
src/types/impl-trait.md
Outdated
} | ||
``` | ||
|
||
This does not "reveal" the hidden type. It binds an explicitly known `i32` type as the hidden type of `Bar` and will error if that's not the hidden type everywhere else, too. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think saying it doesn't reveal the hidden type is confusing when we've not defined what revealing means (or why this is or isn't doing that).
The second part of this line talks about this erroring if it's not the same type elsewhere, but that seems off to me? It seems like at minimum this should go after "Defining scope" or otherwise be restructured, since we really want to detail where this kind of assertion can happen.
src/types/impl-trait.md
Outdated
impl Bar for i32 {} | ||
``` | ||
|
||
This is legal, because `i32` could not possibly be a hidden type of `Foo`, because it doesn't implement `Trait` wich is a requirement for all hypothetical hidden types of `Foo`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is legal, because `i32` could not possibly be a hidden type of `Foo`, because it doesn't implement `Trait` wich is a requirement for all hypothetical hidden types of `Foo`. | |
This is legal, because `i32` could not possibly be a hidden type of `Foo`, because it doesn't implement `Trait` which is a requirement for all hypothetical hidden types of `Foo`. |
src/types/impl-trait.md
Outdated
impl Bar for i32 {} | ||
``` | ||
|
||
This is legal, because `i32` could not possibly be a hidden type of `Foo`, because it doesn't implement `Trait` wich is a requirement for all hypothetical hidden types of `Foo`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find this pretty surprising -- it means adding a trait impl is a direct breaking change, not just because of changes to e.g. method call resolution or similar. Is this limited to the same crate perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is limited to impls for traits in the same crate.
src/types/impl-trait.md
Outdated
#### Defining scope | ||
|
||
Similar to return-position-impl-trait, you can only bind a hidden type of a type-alias-impl-trait within a specific "scope" (henceforth called "defining scope"). | ||
The defining scope of a return-position-impl-trait is the function's body, excluding other items nested within that function's body (we may want to relax that restriction on return-position-impl-trait in the future). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The note around future relaxation seems a little atypical for the reference, but maybe we can keep it.
src/types/impl-trait.md
Outdated
`impl Trait` can only appear as a parameter or return type of a free or inherent function. | ||
It cannot appear inside implementations of traits, nor can it be the type of a let binding or appear inside a type alias. | ||
`impl Trait` can only appear as a parameter or return type of a free or inherent function, within a type alias or within an associated type. | ||
It cannot appear inside implementations of traits, nor can it be the type of a let binding. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "implementations of traits" seems wrong -- associated types are within implementations of traits, right? What do we mean by this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for preparing this! Unfortunately I have mostly editorial nits instead of substantive feedback right now.
|
||
Abstract types are backed by a "hidden type", but only expose certain traits of that hidden type. | ||
Every abstract type has exactly one hidden type, and the hidden type is inaccessible (it could even be a private | ||
type or an unnameable type like a closure). It is required that every abstract type gets a hidden type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you make sure that there is one sentence per line?
@@ -53,6 +65,16 @@ This includes generic arguments for the return type or any const generics. | |||
> | |||
> Therefore, changing the function signature from either one to the other can constitute a breaking change for the callers of a function. | |||
|
|||
## Abstract types | |||
|
|||
> Note: these are also called "existential types" or "opaque types". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Style-wise, I suggest using italics instead of quotes when introducing terms or marking key phrases. I realize the rest of the text isn't always consistent with that, but I think that is the generally accepted way to format these. (There's a few other examples below that could be changed.)
> Note: these are also called "existential types" or "opaque types". | |
> Note: these are also called *existential types* or *opaque types*. |
Abstract types are backed by a "hidden type", but only expose certain traits of that hidden type. | ||
Every abstract type has exactly one hidden type, and the hidden type is inaccessible (it could even be a private | ||
type or an unnameable type like a closure). It is required that every abstract type gets a hidden type | ||
assigned/constrained/bound within its "defining scope" (more to that later). It may get constrained multiple times, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be good to link instead of vaguely referring to some other text.
assigned/constrained/bound within its "defining scope" (more to that later). It may get constrained multiple times, | |
assigned/constrained/bound within its [*defining scope*](#defining-scope). It may get constrained multiple times, |
|
||
> Note: these are also called "existential types" or "opaque types". | ||
|
||
Abstract types are backed by a "hidden type", but only expose certain traits of that hidden type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Abstract types are backed by a "hidden type", but only expose certain traits of that hidden type. | |
Abstract types are backed by a *hidden type*, but only expose certain traits of that hidden type. |
|
||
In contrast to `impl Trait`s in function return types, type aliases can be used in more places than just return types and the same type alias can be used multiple times. | ||
|
||
```rust,ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd generally prefer to avoid using ignore
whenever possible, as it has caused problems in the past. Can you try to remove ignore
from all of the examples? If there is some irrelevant parts needed, they can be prefixed with #
to hide them. For example:
# trait Trait {}
# struct ImplementsTrait;
# impl Trait for ImplementsTrait {}
fn foo() -> impl Trait {
# let value_of_type_that_implements_Trait = ImplementsTrait;
value_of_type_that_implements_Trait
}
When such a type alias is used as the return type of multiple functions, all functions must use the same hidden type. | ||
This is similar to how all code paths in a function with a return-position-impl-trait must return the same type: | ||
|
||
```rust,ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an example explicitly illustrates a failure, they should be marked with compile_fail
. Even better is to include the error code, which will be validated that the correct error is reported.
```rust,ignore | |
```rust,compile_fail,E0404 |
### Binding types | ||
|
||
You can also use type-alias-impl-trait for the type | ||
of local variables, constants, statics, ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is ...
here to be completed?
Also, when possible it is preferred to link to concepts (even if they seem painfully obvious to you).
of local variables, constants, statics, ... | |
of [local variables], [constants], [statics], ... | |
[local variables]: ../variables.md | |
[constants]: ../items/constant-items.md | |
[statics]: ../items/static-items.md |
Every abstract type has exactly one hidden type, and the hidden type is inaccessible (it could even be a private | ||
type or an unnameable type like a closure). It is required that every abstract type gets a hidden type | ||
assigned/constrained/bound within its "defining scope" (more to that later). It may get constrained multiple times, | ||
but each such constraining site must be using the exact same type (to ensure the abstract type has exactly one hidden type). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This mentions constraining but doesn't directly define what it means. Would it be possible to add some kind of definition for that? For example, the generic implementations section has a definition of constrain. Perhaps those are similar enough they can be shared, or perhaps they are different enough that another explicit statement here would be good?
|
||
The defining scope of a type-alias-impl-trait is the scope in which it was defined. So usually a module and all its child items, but it can also be a function body, const initializer and similar scopes that can define items. | ||
|
||
Any use of the type-alias-impl-trait within the defining scope will become a **defining use** (meaning it binds a hidden type), if the type is coerced to or from, equated with, or subtyped with any other concrete type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this maybe add an example that illustrates the consequence of a defining scope of tait? I think there is a connection that I am missing from binding of the hidden type in a defining scope to the limitations now implied of using that type alias outside of that defining scope. (Or perhaps those limitations are the same within and without the defining scope, I'm not clear.)
☔ The latest upstream changes (possibly bf115a4) made this pull request unmergeable. Please resolve the merge conflicts. |
This feature is on track to be stabilized soon: rust-lang/rust#63063
Until the feature is stabilized, CI will fail, because we can't use feature gates in the reference and we can't use TAIT examples without feature gates.