-
Notifications
You must be signed in to change notification settings - Fork 504
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
Add more content to impl-trait.md #1017
Changes from 6 commits
ed438b9
999a143
d9264ab
48d970e
ec1ee92
4531218
1a2f82e
94085bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,33 +5,120 @@ | |
> | ||
> _ImplTraitTypeOneBound_ : `impl` [_TraitBound_] | ||
|
||
## Anonymous type parameters | ||
> **Edition differences**: `impl Trait` is new in the 2018 edition. | ||
|
||
`impl Trait` provides ways to specify unnamed but concrete types that | ||
implement a specific trait. | ||
It can appear in two sorts of places: argument position (where it can act as an anonymous type parameter to functions), and return position (where it can act as an abstract return type). | ||
|
||
> Note: This section is a placeholder for more comprehensive reference | ||
> material. | ||
```rust | ||
trait Trait {} | ||
# impl Trait for () {} | ||
|
||
// argument position: anonymous type parameter | ||
fn foo(arg: impl Trait) { | ||
} | ||
|
||
// return position: abstract return type | ||
fn bar() -> impl Trait { | ||
} | ||
``` | ||
## Anonymous type parameters | ||
|
||
> Note: This is often called "impl Trait in argument position". | ||
|
||
Functions can declare an argument to be an anonymous type parameter where the | ||
callee must provide a type that has the bounds declared by the anonymous type | ||
parameter and the function can only use the methods available by the trait | ||
bounds of the anonymous type parameter. | ||
Functions can use `impl` followed by a set of trait bounds to declare an argument as having an anonymous type. | ||
The caller must provide a type that satisfies the bounds declared by the anonymous type parameter, and the function can only use the methods available through the trait bounds of the anonymous type parameter. | ||
|
||
They are written as `impl` followed by a set of trait bounds. | ||
For example, these two forms are almost equivalent: | ||
|
||
## Abstract return types | ||
```rust,ignore | ||
trait Trait {} | ||
|
||
// generic type parameter | ||
fn foo<T: Trait>(arg: T) { | ||
} | ||
|
||
> Note: This section is a placeholder for more comprehensive reference | ||
> material. | ||
// impl Trait in argument position | ||
fn foo(arg: impl Trait) { | ||
} | ||
``` | ||
|
||
That is, `impl Trait` in argument position is syntactic sugar for a generic type parameter like `<T: Trait>`, except that the type is anonymous and doesn't appear in the [_GenericParams_] list. | ||
|
||
> **Note:** | ||
> For function arguments, generic type parameters and `impl Trait` are not exactly equivalent. | ||
> With a generic parameter such as `<T: Trait>`, the caller has the option to explicitly specify the generic argument for `T` at the call site using [_GenericArgs_], for example, `foo::<usize>(1)`. | ||
> If `impl Trait` is the type of a function argument, then the caller can't ever specify the type of that argument by using a generic argument. | ||
> | ||
> Therefore, changing the function signature from either one to the other can constitute a breaking change for the callers of a function. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see how changing from generic parameter to impl trait would be a breaking change, but can you explain how the reverse is also a breaking change? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this was based on text that I copied from the edition guide. For the reverse direction, I'm not especially familiar with using turbofish when there are multiple type parameters, e.g., what if we went from fn foo<T: PartialOrd>(a: T, b: impl PartialOrd) {}
foo::<i32>(0, 1.0); to fn foo<T: PartialOrd, U: PartialOrd>(a: T, b: U) {}
foo::<i32>(0, 1.0); Is it valid to omit the second type parameter? How does the compiler know which one is omitted? (I can't find any text in the reference about this case.) I'm sorry if this is a bad example. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I experimented a bit in the playground, and it seems like in practice:
Side note: are we avoiding using the term "turbofish" in the reference? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. The second parameter can be omitted if it is defaulted, like The reference normally doesn't discuss semver compatibility since that is a cargo concept (documented here). It's probably fine to leave this as-is, though. There is one mention of turbofish, plus another in the glossary, but otherwise it usually doesn't come up. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some further experimentation suggests that both my proposed text and the edition guide are differently wrong:
|
||
|
||
## Abstract return types | ||
|
||
> Note: This is often called "impl Trait in return position". | ||
|
||
Functions, except for associated trait functions, can return an abstract | ||
return type. These types stand in for another concrete type where the | ||
use-site may only use the trait methods declared by the trait bounds of the | ||
type. | ||
Functions can use `impl Trait` to return an abstract return type. | ||
These types stand in for another concrete type where the caller may only use the methods declared by the specified `Trait`. | ||
Each possible return value from the function must resolve to the same concrete type. | ||
|
||
`impl Trait` in return position allows a function to return an unboxed abstract type. | ||
This is particularly useful with [closures] and iterators. | ||
For example, closures have a unique, un-writable type. | ||
Previously, the only way to return a closure from a function was to use a [trait object]: | ||
|
||
```rust | ||
fn returns_closure() -> Box<dyn Fn(i32) -> i32> { | ||
Box::new(|x| x + 1) | ||
} | ||
``` | ||
|
||
This could incur performance penalties from heap allocation and dynamic dispatch. | ||
It wasn't possible to fully specify the type of the closure, only to use the `Fn` trait. | ||
That means that the trait object is necessary. | ||
However, with `impl Trait`, it is possible to write this more simply: | ||
|
||
```rust | ||
fn returns_closure() -> impl Fn(i32) -> i32 { | ||
|x| x + 1 | ||
} | ||
``` | ||
|
||
which also avoids the drawbacks of using a boxed trait object. | ||
|
||
Similarly, the concrete types of iterators could become very complex, incorporating the types of all previous iterators in a chain. | ||
Returning `impl Iterator` means that a function only exposes the `Iterator` trait as a bound on its return type, instead of explicitly specifying all of the other iterator types involved. | ||
|
||
### Differences between generics and `impl Trait` in return position | ||
|
||
In argument position, `impl Trait` is very similar in semantics to a generic type parameter. | ||
However, there are significant differences between the two in return position. | ||
With `impl Trait`, unlike with a generic type parameter, the function chooses the return type, and the caller cannot choose the return type. | ||
|
||
The function: | ||
|
||
```rust,ignore | ||
fn foo<T: Trait>() -> T { | ||
``` | ||
|
||
allows the caller to determine the return type, `T`, and the function returns that type. | ||
|
||
The function: | ||
|
||
```rust,ignore | ||
fn foo() -> impl Trait { | ||
``` | ||
|
||
doesn't allow the caller to determine the return type. | ||
Instead, the function chooses the return type, but only promises that it will implement `Trait`. | ||
|
||
## Limitations | ||
|
||
They are written as `impl` followed by a set of trait bounds. | ||
`impl Trait` can only appear as the argument 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. | ||
|
||
[closures]: closure.md | ||
[_GenericArgs_]: ../paths.md#paths-in-expressions | ||
[_GenericParams_]: ../items/generics.md | ||
[_TraitBound_]: ../trait-bounds.md | ||
[trait object]: trait-object.md | ||
[_TypeParamBounds_]: ../trait-bounds.md |
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 edition differences are only for things that change compilation based on the edition. The old style of the edition guide presenting all new features as part of the "edition" is being phased out.
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.
Thanks for the review! I had seen other parts of the reference using language like this. Is text like "
impl Trait
is not available in the 2015 edition" better? I think I saw some style guide saying to prefer that, but I can't seem to find it now.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.
impl trait can be used on both 2015 and 2018. The only differences between the editions are listed here: https://doc.rust-lang.org/edition-guide/rust-2018/edition-changes.html
The reference conventions are listed here and there is a style guide here.
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.
Oh, I see. Apparently some new features were enabled in the 2015 edition after 2015, and only some (with syntax backward-incompatibilities?) were gated on edition=2018? I'm fine with deleting that note, then. (Though I also think the edition guide could use some better explanation about how new features found their way into the 2015 edition.)
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.
Yea. Usually new features work on all editions if possible. Only if it is backwards-incompatible in some way does it get gated on a new edition. FWIW, the edition guide is in the process of being rewritten.