From bdd6d224a093bd3940ba42370f66e324b7df2157 Mon Sep 17 00:00:00 2001 From: Alexis Hunt Date: Mon, 19 Feb 2018 19:46:19 -0500 Subject: [PATCH 1/4] Expand on mechanics of closure types. --- src/types.md | 137 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 117 insertions(+), 20 deletions(-) diff --git a/src/types.md b/src/types.md index 60eb470fc..aa28887ac 100644 --- a/src/types.md +++ b/src/types.md @@ -365,30 +365,99 @@ x = bo(5,7); ## Closure types A [closure expression] produces a closure value with a unique, anonymous type -that cannot be written out. +that cannot be written out. A closure type is approximately equivalent to a +struct which contains the captured variables. For instance, the following +closure: -Depending on the requirements of the closure, its type implements one or -more of the closure traits: +```rust +fn f String> (g: F) { + println!("{}", g()); +} + +let mut s = String::from("foo"); +let t = String::from("bar"); + +f(|| { + s += t; + s +}); +// Prints "foobar". +``` + +generates a closure type roughly like the following: + +```rust +struct Closure<'a> { + s : String + t : &'a String +} -* `FnOnce` - : The closure can be called once. A closure called as `FnOnce` can move out - of its captured values. +impl<'a> FnOnce() -> String for Closure<'a> { + fn call_once(self) -> String { + self.s += self.t; + self.s + } +} +``` -* `FnMut` - : The closure can be called multiple times as mutable. A closure called as - `FnMut` can mutate values from its environment. `FnMut` inherits from - `FnOnce` (i.e. anything implementing `FnMut` also implements `FnOnce`). +so that the call to `f` works as if it were: -* `Fn` : The closure can be called multiple times through a shared reference. A - closure called as `Fn` can neither move out from nor mutate captured - variables, but read-only access to such values is allowed. Using `move` to - capture variables by value is allowed so long as they aren't mutated or - moved in the body of the closure. `Fn` inherits from `FnMut`, which itself - inherits from `FnOnce`. +```rust,ignore +f(Closure{s: s, t: &t}); +``` -Closures that don't use anything from their environment, called *non-capturing -closures*, can be coerced to function pointers (`fn`) with the matching -signature. To adopt the example from the section above: +The compiler prefers to capture a closed-over variable by immutable borrow, +followed by mutable borrow and finally by move (or copy, for [`Copy`] types). It +will pick the first choice of these that allows the closure to compile. If the +`move` keyword is used, then all captures are by move or copy, regardless of +whether a borrow would work. The `move` keyword is usually used to allow the +closure to outlive the captured values, such as if the closure is being returned +or used to spawn a new thread. + +Structs and tuples are always captured entirely, not by individual fields. It +may be necessary to borrow into a local variable in order to capture a single +field: + +```rust +struct SetVec { + set: HashSet, + vec: Vec +} + +impl Pair { + fn populate(&mut self) { + let vec = &mut self.vec; + self.set.iter().for_each(|&n| { + vec.push(n); + }) + } +} +``` + +If, instead, the closure were to use `self.vec` directly, then it would attempt +to capture `self` by mutable reference. But since `self.set` is already +borrowed to iterate over, the closure would not compile. + +### Call traits and coercions + +Closure types all implement `[FnOnce]`, indicating that they can be called once +by consuming ownership of the closure. Additionally, some closures implement +more specific call traits: + +* A closure which does not move out of any captured variables implements + `[FnMut]`, indicating that it can be called by mutable reference. + +* A closure which does not mutate or move out of any captured variables + implements `[Fn]`, indicating that it can be called by shared reference. + +> Note that `move` closures may still implement `[Fn]` or `[FnMut]`, even +> though they capture variables by move. This is because the traits +> implemented by a closure type are determined by what the closure does with +> captured values, not how it captures them. + +In addition to the call traits, *non-capturing closures*---those that don't +capture anything from their environment---can be coerced to function pointers +(`fn`) with the matching signature. ```rust let add = |x, y| x + y; @@ -400,6 +469,33 @@ let bo: Binop = add; x = bo(5,7); ``` +### Other traits + +Closure types implement the following traits, if allowed to do so by the +captured values: + +* `[Sized]` +* `[Send]` +* `[Sync]` +* `[Clone]` +* `[Copy]` + +`[Sized]` is always implemented (local variables are all sized, so all captured +values must be too). The rules for `[Send]` and `[Sync]` match those for normal +struct types, while `[Clone]` and `[Copy]` behave as if [derived][derive]. For +`[Clone]`, the order of cloning of the captured variables is left unspecified. + +Because captures are often by reference, the following general rules arise: + +* All closures are `[Sized]`. +* A closure is `[Sync]` if all values captured by mutable reference, move, or + copy are `[Sync]`. +* A closure is `[Send]` if all values captured by shared reference are `[Sync]`, + and all values captured by mutable reference, move, or copy are `[Send]`. +* A closure is `[Clone]` or `[Copy]` if it does not capture any values by + mutable reference, and if all values it captures by move or copy are `[Clone]` + or `[Copy]`, respectively. + ## Trait objects A *trait object* is an opaque value of another type that implements a set of @@ -593,6 +689,7 @@ impl Printable for String { [Clone]: special-types-and-traits.html#clone [Send]: special-types-and-traits.html#send [Sync]: special-types-and-traits.html#sync +[derive]: attributes.html#derive [`Vec`]: ../std/vec/struct.Vec.html [dynamically sized type]: dynamically-sized-types.html [dynamically sized types]: dynamically-sized-types.html @@ -603,4 +700,4 @@ impl Printable for String { [auto traits]: special-types-and-traits.html#auto-traits [object safe]: items/traits.html#object-safety [issue 47010]: https://github.com/rust-lang/rust/issues/47010 -[issue 33140]: https://github.com/rust-lang/rust/issues/33140 \ No newline at end of file +[issue 33140]: https://github.com/rust-lang/rust/issues/33140 From 82a78c95b81f560b5f3258dee16aae9a4f042b18 Mon Sep 17 00:00:00 2001 From: Alexis Hunt Date: Tue, 20 Feb 2018 21:05:18 -0500 Subject: [PATCH 2/4] Fix compile errors. --- src/types.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/types.md b/src/types.md index aa28887ac..d13d430a3 100644 --- a/src/types.md +++ b/src/types.md @@ -378,7 +378,7 @@ let mut s = String::from("foo"); let t = String::from("bar"); f(|| { - s += t; + s += &*t; s }); // Prints "foobar". @@ -386,15 +386,15 @@ f(|| { generates a closure type roughly like the following: -```rust +```rust,ignore struct Closure<'a> { - s : String - t : &'a String + s : String, + t : &'a String, } -impl<'a> FnOnce() -> String for Closure<'a> { +impl<'a> (FnOnce() -> String) for Closure<'a> { fn call_once(self) -> String { - self.s += self.t; + self.s += &*self.t; self.s } } @@ -419,12 +419,14 @@ may be necessary to borrow into a local variable in order to capture a single field: ```rust +# use std::collections::HashSet; +# struct SetVec { set: HashSet, vec: Vec } -impl Pair { +impl SetVec { fn populate(&mut self) { let vec = &mut self.vec; self.set.iter().for_each(|&n| { From a84f4f7a8f136672f9f0671d3c67961575e28cc7 Mon Sep 17 00:00:00 2001 From: Alexis Hunt Date: Fri, 23 Feb 2018 19:24:20 -0500 Subject: [PATCH 3/4] Havvy's review feedback on closure types. --- src/types.md | 56 +++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/src/types.md b/src/types.md index d13d430a3..1ceaa4229 100644 --- a/src/types.md +++ b/src/types.md @@ -407,16 +407,16 @@ f(Closure{s: s, t: &t}); ``` The compiler prefers to capture a closed-over variable by immutable borrow, -followed by mutable borrow and finally by move (or copy, for [`Copy`] types). It -will pick the first choice of these that allows the closure to compile. If the -`move` keyword is used, then all captures are by move or copy, regardless of -whether a borrow would work. The `move` keyword is usually used to allow the -closure to outlive the captured values, such as if the closure is being returned -or used to spawn a new thread. +followed by mutable borrow, by copy, and finally by move. It will pick the first +choice of these that allows the closure to compile. If the `move` keyword is +used, then all captures are by move or copy, regardless of whether a borrow +would work. The `move` keyword is usually used to allow the closure to outlive +the captured values, such as if the closure is being returned or used to spawn a +new thread. -Structs and tuples are always captured entirely, not by individual fields. It -may be necessary to borrow into a local variable in order to capture a single -field: +Composite types such as structs, tuples, and enums are always captured entirely, +not by individual fields. It may be necessary to borrow into a local variable in +order to capture a single field: ```rust # use std::collections::HashSet; @@ -438,7 +438,7 @@ impl SetVec { If, instead, the closure were to use `self.vec` directly, then it would attempt to capture `self` by mutable reference. But since `self.set` is already -borrowed to iterate over, the closure would not compile. +borrowed to iterate over, the code would not compile. ### Call traits and coercions @@ -453,13 +453,13 @@ more specific call traits: implements `[Fn]`, indicating that it can be called by shared reference. > Note that `move` closures may still implement `[Fn]` or `[FnMut]`, even -> though they capture variables by move. This is because the traits +> though they capture variables by move: this is because the traits > implemented by a closure type are determined by what the closure does with > captured values, not how it captures them. -In addition to the call traits, *non-capturing closures*---those that don't -capture anything from their environment---can be coerced to function pointers -(`fn`) with the matching signature. +*Non-capturing closures* are closures that don't capture anything from their +environment. They can be coerced to function pointers (`fn`) with the matching +signature. ```rust let add = |x, y| x + y; @@ -473,29 +473,27 @@ x = bo(5,7); ### Other traits -Closure types implement the following traits, if allowed to do so by the -captured values: +All closure types implement `[Sized]`. Additionally, closure types implement the +following traits if allowed to do so by the types of the captures it stores: -* `[Sized]` -* `[Send]` -* `[Sync]` * `[Clone]` * `[Copy]` +* `[Sync]` +* `[Send]` -`[Sized]` is always implemented (local variables are all sized, so all captured -values must be too). The rules for `[Send]` and `[Sync]` match those for normal -struct types, while `[Clone]` and `[Copy]` behave as if [derived][derive]. For -`[Clone]`, the order of cloning of the captured variables is left unspecified. +The rules for `[Send]` and `[Sync]` match those for normal struct types, while +`[Clone]` and `[Copy]` behave as if [derived][derive]. For `[Clone]`, the order +of cloning of the captured variables is left unspecified. Because captures are often by reference, the following general rules arise: -* All closures are `[Sized]`. -* A closure is `[Sync]` if all values captured by mutable reference, move, or - copy are `[Sync]`. -* A closure is `[Send]` if all values captured by shared reference are `[Sync]`, - and all values captured by mutable reference, move, or copy are `[Send]`. +* A closure is `[Sync]` if all variables captured by mutable reference, copy, or + move are `[Sync]`. +* A closure is `[Send]` if all variables captured by shared reference are + `[Sync]`, and all values captured by mutable reference, copy, or move are + `[Send]`. * A closure is `[Clone]` or `[Copy]` if it does not capture any values by - mutable reference, and if all values it captures by move or copy are `[Clone]` + mutable reference, and if all values it captures by copy or move are `[Clone]` or `[Copy]`, respectively. ## Trait objects From 07ccc1d5f400124ce2eb6e3ba7e429c963b7c8cc Mon Sep 17 00:00:00 2001 From: Alexis Hunt Date: Fri, 23 Feb 2018 20:02:36 -0500 Subject: [PATCH 4/4] Fix colon placement in note. --- src/types.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types.md b/src/types.md index 1ceaa4229..faf896b27 100644 --- a/src/types.md +++ b/src/types.md @@ -452,10 +452,10 @@ more specific call traits: * A closure which does not mutate or move out of any captured variables implements `[Fn]`, indicating that it can be called by shared reference. -> Note that `move` closures may still implement `[Fn]` or `[FnMut]`, even -> though they capture variables by move: this is because the traits -> implemented by a closure type are determined by what the closure does with -> captured values, not how it captures them. +> Note: `move` closures may still implement `[Fn]` or `[FnMut]`, even though +> they capture variables by move. This is because the traits implemented by a +> closure type are determined by what the closure does with captured values, not +> how it captures them. *Non-capturing closures* are closures that don't capture anything from their environment. They can be coerced to function pointers (`fn`) with the matching