From ed438b9dc221429f3f62d756375050dc71f5f0ca Mon Sep 17 00:00:00 2001 From: Taylor Yu Date: Wed, 5 May 2021 20:19:29 -0500 Subject: [PATCH 1/8] Update impl-trait from edition-guide Add impl-trait text from edition-guide, mostly unchanged except for reformatting to one sentence per line. --- src/types/impl-trait.md | 152 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 146 insertions(+), 6 deletions(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index d7beeda7a..0e4ba5711 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -5,10 +5,22 @@ > > _ImplTraitTypeOneBound_ : `impl` [_TraitBound_] -## Anonymous type parameters +`impl Trait` is the new way to specify unnamed but concrete types that +implement a specific trait. +There are two places you can put it: argument position, and return position. + +```rust,ignore +trait Trait {} + +// argument position +fn foo(arg: impl Trait) { +} -> Note: This section is a placeholder for more comprehensive reference -> material. +// return position +fn foo() -> impl Trait { +} +``` +## Anonymous type parameters > Note: This is often called "impl Trait in argument position". @@ -18,11 +30,28 @@ parameter and the function can only use the methods available by the trait bounds of the anonymous type parameter. They are written as `impl` followed by a set of trait bounds. +In argument position, this feature is quite simple. +These two forms are almost the same: -## Abstract return types +```rust,ignore +trait Trait {} + +fn foo(arg: T) { +} -> Note: This section is a placeholder for more comprehensive reference -> material. +fn foo(arg: impl Trait) { +} +``` + +That is, it's a slightly shorter syntax for a generic type parameter. +It means, "`arg` is an argument that takes any type that implements the `Trait` trait." + +However, there's also an important technical difference between `T: Trait` and `impl Trait` here. +When you write the former, you can specify the type of `T` at the call site with turbo-fish syntax as with `foo::(1)`. +In the case of `impl Trait`, if it is used anywhere in the function definition, then you can't use turbo-fish at all. +Therefore, you should be mindful that changing both from and to `impl Trait` can constitute a breaking change for the users of your code. + +## Abstract return types > Note: This is often called "impl Trait in return position". @@ -33,5 +62,116 @@ type. They are written as `impl` followed by a set of trait bounds. +Before `impl Trait`, you could do this with trait objects: + +```rust +trait Trait {} + +impl Trait for i32 {} + +fn returns_a_trait_object() -> Box { + Box::new(5) +} +``` + +However, this has some overhead: the `Box` means that there's a heap allocation here, and this will use dynamic dispatch. +See the `dyn Trait` section for an explanation of this syntax. +But we only ever return one possible thing here, the `Box`. +This means that we're paying for dynamic dispatch, even though we don't use it! + +With `impl Trait`, the code above could be written like this: + +```rust +trait Trait {} + +impl Trait for i32 {} + +fn returns_a_trait_object() -> impl Trait { + 5 +} +``` + +Here, we have no `Box`, no trait object, and no dynamic dispatch. +But we still can obscure the `i32` return type. + +With `i32`, this isn't super useful. +But there's one major place in Rust where this is much more useful: closures. + +### `impl Trait` and closures + +> If you need to catch up on closures, check out [their chapter in the +> book](https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html). + +In Rust, closures have a unique, un-writable type. +They do implement the `Fn` family of traits, however. +This means that previously, the only way to return a closure from a function was to use a trait object: + +```rust +fn returns_closure() -> Box i32> { + Box::new(|x| x + 1) +} +``` + +You couldn't write the type of the closure, only use the `Fn` trait. +That means that the trait object is necessary. However, with `impl Trait`: + +```rust +fn returns_closure() -> impl Fn(i32) -> i32 { + |x| x + 1 +} +``` + +We can now return closures by value, just like any other type! + +## More details + +The above is all you need to know to get going with `impl Trait`, but for some more nitty-gritty details: type parameters and `impl Trait` work slightly differently when they're in argument position versus return position. +Consider this function: + +```rust,ignore +fn foo(x: T) { +``` + +When you call it, you set the type, `T`. +"you" being the caller here. +This signature says "I accept any type that implements `Trait`." +("any type" == universal in the jargon) + +This version: + +```rust,ignore +fn foo() -> T { +``` + +is similar, but also different. +You, the caller, provide the type you want, `T`, and then the function returns it. +You can see this in Rust today with things like parse or collect: + +```rust,ignore +let x: i32 = "5".parse()?; +let x: u64 = "5".parse()?; +``` + +Here, `.parse` has this signature: + +```rust,ignore +pub fn parse(&self) -> Result::Err> where + F: FromStr, +``` + +Same general idea, though with a result type and `FromStr` has an associated type... anyway, you can see how `F` is in the return position here. +So you have the ability to choose. + +With `impl Trait`, you're saying "hey, some type exists that implements this trait, but I'm not gonna tell you what it is." +So now, the caller can't choose, and the function itself gets to choose. +If we tried to define parse with `Result Date: Wed, 5 May 2021 20:19:29 -0500 Subject: [PATCH 2/8] Fix typo(impl-trait) Other documentation makes it reasonably clear that `impl Trait` in argument position allows the _caller_ to specify the type. --- src/types/impl-trait.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index 0e4ba5711..3aa2f8d81 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -25,7 +25,7 @@ fn foo() -> impl Trait { > 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 +caller 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. From d9264ab14376cfa340101a922abafbf7e5bdc119 Mon Sep 17 00:00:00 2001 From: Taylor Yu Date: Wed, 5 May 2021 20:19:29 -0500 Subject: [PATCH 3/8] impl-trait: less update-y; more reference-y Reword a lot of things to sound less like an update and more like a reference. Add local links in a few places. --- src/types/impl-trait.md | 100 ++++++++++++++++++++-------------------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index 3aa2f8d81..17664a3be 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -5,64 +5,63 @@ > > _ImplTraitTypeOneBound_ : `impl` [_TraitBound_] -`impl Trait` is the new way to specify unnamed but concrete types that +> **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. -There are two places you can put it: argument position, and return position. +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). -```rust,ignore +```rust trait Trait {} +# impl Trait for () {} -// argument position +// argument position: anonymous type parameter fn foo(arg: impl Trait) { } -// return position -fn foo() -> 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 -caller 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. -In argument position, this feature is quite simple. -These two forms are almost the same: +For example, these two forms are almost equivalent: ```rust,ignore trait Trait {} +// generic type parameter fn foo(arg: T) { } +// impl Trait in argument position fn foo(arg: impl Trait) { } ``` -That is, it's a slightly shorter syntax for a generic type parameter. -It means, "`arg` is an argument that takes any type that implements the `Trait` trait." +That is, `impl Trait` in argument position is syntactic sugar for a generic type parameter like ``, except that the type is anonymous and doesn't appear in the [_GenericParams_] list. -However, there's also an important technical difference between `T: Trait` and `impl Trait` here. -When you write the former, you can specify the type of `T` at the call site with turbo-fish syntax as with `foo::(1)`. -In the case of `impl Trait`, if it is used anywhere in the function definition, then you can't use turbo-fish at all. -Therefore, you should be mindful that changing both from and to `impl Trait` can constitute a breaking change for the users of your code. +> **Note:** +> For function arguments, generic type parameters and `impl Trait` are not exactly equivalent. +> With a generic parameter such as ``, the caller has the option to explicitly specify the generic argument for `T` at the call site using [_GenericArgs_], for example, `foo::(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. ## 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. - -They are written as `impl` followed by a set of trait bounds. +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. -Before `impl Trait`, you could do this with trait objects: +Prior to `impl Trait`, a function could express abstract return types by using [trait objects]: ```rust trait Trait {} @@ -74,10 +73,9 @@ fn returns_a_trait_object() -> Box { } ``` -However, this has some overhead: the `Box` means that there's a heap allocation here, and this will use dynamic dispatch. -See the `dyn Trait` section for an explanation of this syntax. -But we only ever return one possible thing here, the `Box`. -This means that we're paying for dynamic dispatch, even though we don't use it! +This has some drawbacks: constructing `Box` involves a heap allocation, and the `dyn Trait` will use dynamic dispatch on its methods. +However, this function only returns one possible type here: the `Box`. +This means incurring the costs of dynamic dispatch, even though the return type cannot vary. With `impl Trait`, the code above could be written like this: @@ -91,19 +89,16 @@ fn returns_a_trait_object() -> impl Trait { } ``` -Here, we have no `Box`, no trait object, and no dynamic dispatch. -But we still can obscure the `i32` return type. +There is no `Box`, no trait object, and no dynamic dispatch. +However, the function can still can obscure the `i32` return type. -With `i32`, this isn't super useful. -But there's one major place in Rust where this is much more useful: closures. +With `i32`, this might not seem very useful. +There is one major place in Rust where this is much more useful: closures. ### `impl Trait` and closures -> If you need to catch up on closures, check out [their chapter in the -> book](https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html). - -In Rust, closures have a unique, un-writable type. -They do implement the `Fn` family of traits, however. +In Rust, [closures] have a unique, un-writable type. +However, they do implement the `Fn` family of traits. This means that previously, the only way to return a closure from a function was to use a trait object: ```rust @@ -112,8 +107,9 @@ fn returns_closure() -> Box i32> { } ``` -You couldn't write the type of the closure, only use the `Fn` trait. -That means that the trait object is necessary. However, with `impl Trait`: +It wasn't possible to fully specify the type of the closure, only use the `Fn` trait. +That means that the trait object is necessary. +However, with `impl Trait`: ```rust fn returns_closure() -> impl Fn(i32) -> i32 { @@ -121,7 +117,7 @@ fn returns_closure() -> impl Fn(i32) -> i32 { } ``` -We can now return closures by value, just like any other type! +It is now possible to return closures by value, just like any other type. ## More details @@ -132,10 +128,8 @@ Consider this function: fn foo(x: T) { ``` -When you call it, you set the type, `T`. -"you" being the caller here. -This signature says "I accept any type that implements `Trait`." -("any type" == universal in the jargon) +The caller of this function determines the type, `T`. +This function signature means that the function accepts any type that implements `Trait`." This version: @@ -144,15 +138,15 @@ fn foo() -> T { ``` is similar, but also different. -You, the caller, provide the type you want, `T`, and then the function returns it. -You can see this in Rust today with things like parse or collect: +The caller determines the return type, `T`, and the function returns it. +Examples of this include the `.parse()` or `.collect()` methods: ```rust,ignore let x: i32 = "5".parse()?; let x: u64 = "5".parse()?; ``` -Here, `.parse` has this signature: +Here, `.parse()` has this signature: ```rust,ignore pub fn parse(&self) -> Result::Err> where @@ -162,8 +156,8 @@ pub fn parse(&self) -> Result::Err> where Same general idea, though with a result type and `FromStr` has an associated type... anyway, you can see how `F` is in the return position here. So you have the ability to choose. -With `impl Trait`, you're saying "hey, some type exists that implements this trait, but I'm not gonna tell you what it is." -So now, the caller can't choose, and the function itself gets to choose. +With `impl Trait`, the function asserts that the return type will implement this trait, but the caller can't know exactly which type. +So with `impl Trait`, unlike with a generic type parameter for the return type, the caller can't choose the return type, and the function itself gets to choose. If we tried to define parse with `Result Date: Wed, 5 May 2021 20:19:29 -0500 Subject: [PATCH 4/8] impl-trait: don't talk about future Rust Rename and rework the "Using `impl Trait` in more places" into a "Limitations" section. --- src/types/impl-trait.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index 17664a3be..0f37b4ff4 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -160,12 +160,10 @@ With `impl Trait`, the function asserts that the return type will implement this So with `impl Trait`, unlike with a generic type parameter for the return type, the caller can't choose the return type, and the function itself gets to choose. If we tried to define parse with `Result Date: Wed, 5 May 2021 20:19:29 -0500 Subject: [PATCH 5/8] impl-trait: generics vs `impl Trait` as return Rework "More details" to focus on the differences between `impl Trait` and generic type parameters in return position. Delete the `.parse()` examples because they're somewhat verbose for a reference. --- src/types/impl-trait.md | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index 0f37b4ff4..0872283bf 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -119,46 +119,28 @@ fn returns_closure() -> impl Fn(i32) -> i32 { It is now possible to return closures by value, just like any other type. -## More details +### Differences between generics and `impl Trait` in return position -The above is all you need to know to get going with `impl Trait`, but for some more nitty-gritty details: type parameters and `impl Trait` work slightly differently when they're in argument position versus return position. -Consider this function: +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. -```rust,ignore -fn foo(x: T) { -``` - -The caller of this function determines the type, `T`. -This function signature means that the function accepts any type that implements `Trait`." - -This version: +The function: ```rust,ignore fn foo() -> T { ``` -is similar, but also different. -The caller determines the return type, `T`, and the function returns it. -Examples of this include the `.parse()` or `.collect()` methods: +allows the caller to determine the return type, `T`, and the function returns that type. -```rust,ignore -let x: i32 = "5".parse()?; -let x: u64 = "5".parse()?; -``` - -Here, `.parse()` has this signature: +The function: ```rust,ignore -pub fn parse(&self) -> Result::Err> where - F: FromStr, +fn foo() -> impl Trait { ``` -Same general idea, though with a result type and `FromStr` has an associated type... anyway, you can see how `F` is in the return position here. -So you have the ability to choose. - -With `impl Trait`, the function asserts that the return type will implement this trait, but the caller can't know exactly which type. -So with `impl Trait`, unlike with a generic type parameter for the return type, the caller can't choose the return type, and the function itself gets to choose. -If we tried to define parse with `Result Date: Wed, 5 May 2021 20:19:29 -0500 Subject: [PATCH 6/8] impl-trait: condense return type examples Use only the closure example to illustrate the difference between returning trait objects vs `impl Trait`. Also mention usefulness in returning iterators. --- src/types/impl-trait.md | 55 +++++++++-------------------------------- 1 file changed, 12 insertions(+), 43 deletions(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index 0872283bf..945dc0e07 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -61,45 +61,10 @@ 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. -Prior to `impl Trait`, a function could express abstract return types by using [trait objects]: - -```rust -trait Trait {} - -impl Trait for i32 {} - -fn returns_a_trait_object() -> Box { - Box::new(5) -} -``` - -This has some drawbacks: constructing `Box` involves a heap allocation, and the `dyn Trait` will use dynamic dispatch on its methods. -However, this function only returns one possible type here: the `Box`. -This means incurring the costs of dynamic dispatch, even though the return type cannot vary. - -With `impl Trait`, the code above could be written like this: - -```rust -trait Trait {} - -impl Trait for i32 {} - -fn returns_a_trait_object() -> impl Trait { - 5 -} -``` - -There is no `Box`, no trait object, and no dynamic dispatch. -However, the function can still can obscure the `i32` return type. - -With `i32`, this might not seem very useful. -There is one major place in Rust where this is much more useful: closures. - -### `impl Trait` and closures - -In Rust, [closures] have a unique, un-writable type. -However, they do implement the `Fn` family of traits. -This means that previously, the only way to return a closure from a function was to use a trait object: +`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 i32> { @@ -107,9 +72,10 @@ fn returns_closure() -> Box i32> { } ``` -It wasn't possible to fully specify the type of the closure, only use the `Fn` trait. +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`: +However, with `impl Trait`, it is possible to write this more simply: ```rust fn returns_closure() -> impl Fn(i32) -> i32 { @@ -117,7 +83,10 @@ fn returns_closure() -> impl Fn(i32) -> i32 { } ``` -It is now possible to return closures by value, just like any other type. +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 @@ -151,5 +120,5 @@ It cannot appear inside implementations of traits, nor can it be the type of a l [_GenericArgs_]: ../paths.md#paths-in-expressions [_GenericParams_]: ../items/generics.md [_TraitBound_]: ../trait-bounds.md -[trait objects]: trait-object.md +[trait object]: trait-object.md [_TypeParamBounds_]: ../trait-bounds.md From 1a2f82e7092ee56b2c8cab69dbab8a98c914749f Mon Sep 17 00:00:00 2001 From: Taylor Yu Date: Wed, 19 May 2021 22:42:56 -0500 Subject: [PATCH 7/8] address PR feedback --- src/types/impl-trait.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index 945dc0e07..09a2edd67 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -5,8 +5,6 @@ > > _ImplTraitTypeOneBound_ : `impl` [_TraitBound_] -> **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). @@ -49,7 +47,8 @@ That is, `impl Trait` in argument position is syntactic sugar for a generic type > **Note:** > For function arguments, generic type parameters and `impl Trait` are not exactly equivalent. > With a generic parameter such as ``, the caller has the option to explicitly specify the generic argument for `T` at the call site using [_GenericArgs_], for example, `foo::(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. +> If `impl Trait` is the type of *any* function argument, then the caller can't ever provide any generic arguments when calling that function. +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. From 94085bbf726d53f3c7ace1ce122c82ab7b691d39 Mon Sep 17 00:00:00 2001 From: Taylor Yu Date: Thu, 20 May 2021 16:14:17 -0500 Subject: [PATCH 8/8] use "argument", "parameter" more consistently Be more consistent about using "argument" to mean a concrete value provided by a function caller, and "parameter" to mean the abstract input to the function. Retain a few instances of the phrasing "in argument position", because it appears in the RFCs and implementation. Also make a note about the historical terminology inconsistency. --- src/types/impl-trait.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/types/impl-trait.md b/src/types/impl-trait.md index 09a2edd67..413f999f8 100644 --- a/src/types/impl-trait.md +++ b/src/types/impl-trait.md @@ -24,8 +24,9 @@ fn bar() -> impl Trait { ## Anonymous type parameters > Note: This is often called "impl Trait in argument position". +(The term "parameter" is more correct here, but "impl Trait in argument position" is the phrasing used during the development of this feature, and it remains in parts of the implementation.) -Functions can use `impl` followed by a set of trait bounds to declare an argument as having an anonymous type. +Functions can use `impl` followed by a set of trait bounds to declare a parameter 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. For example, these two forms are almost equivalent: @@ -45,9 +46,9 @@ fn foo(arg: impl Trait) { That is, `impl Trait` in argument position is syntactic sugar for a generic type parameter like ``, 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. +> For function parameters, generic type parameters and `impl Trait` are not exactly equivalent. > With a generic parameter such as ``, the caller has the option to explicitly specify the generic argument for `T` at the call site using [_GenericArgs_], for example, `foo::(1)`. -> If `impl Trait` is the type of *any* function argument, then the caller can't ever provide any generic arguments when calling that function. +> If `impl Trait` is the type of *any* function parameter, then the caller can't ever provide any generic arguments when calling that function. 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. @@ -112,7 +113,7 @@ Instead, the function chooses the return type, but only promises that it will im ## Limitations -`impl Trait` can only appear as the argument or return type of a free or inherent function. +`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. [closures]: closure.md