Skip to content

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

oli-obk
Copy link
Contributor

@oli-obk oli-obk commented Jan 10, 2023

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.

}
```

When such a type alias is used as the return type of multiple functions, all functions must use the same hidden type.
Copy link
Member

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.

Copy link
Contributor Author

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?


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:
Copy link
Member

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."

}
```

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.
Copy link
Member

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.

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`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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`.

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`.
Copy link
Member

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?

Copy link
Contributor Author

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.

#### 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).
Copy link
Member

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.

`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.
Copy link
Member

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?

Copy link
Contributor

@ehuss ehuss left a 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
Copy link
Contributor

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".
Copy link
Contributor

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.)

Suggested change
> 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,
Copy link
Contributor

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.

Suggested change
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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
Copy link
Contributor

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
Copy link
Contributor

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.

Suggested change
```rust,ignore
```rust,compile_fail,E0404

### Binding types

You can also use type-alias-impl-trait for the type
of local variables, constants, statics, ...
Copy link
Contributor

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).

Suggested change
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).
Copy link
Contributor

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.
Copy link
Contributor

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.)

@ehuss ehuss added the S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository label Aug 27, 2024
@rustbot
Copy link
Collaborator

rustbot commented Dec 3, 2024

☔ The latest upstream changes (possibly bf115a4) made this pull request unmergeable. Please resolve the merge conflicts.

@rustbot rustbot added the S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. label Dec 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: The marked PR is awaiting some action (such as code changes) from the PR author. S-waiting-on-stabilization Waiting for a stabilization PR to be merged in the main Rust repository
Projects
Development

Successfully merging this pull request may close these issues.

4 participants