From 7475f06385de47a041c838f5f1ea9aca81a5bb61 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Tue, 28 Nov 2017 19:45:39 -0700 Subject: [PATCH 01/10] initial capture disjoint fields RFC --- text/0000-capture-disjoint-fields.md | 196 +++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 text/0000-capture-disjoint-fields.md diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md new file mode 100644 index 00000000000..3b9b5e82db4 --- /dev/null +++ b/text/0000-capture-disjoint-fields.md @@ -0,0 +1,196 @@ +- Feature Name: capture-disjoint-fields +- Start Date: 2017-11-28 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This RFC proposes that closure capturing should be minimal rather than maximal. +Specifically, existing rules regarding borrowing and moving disjoint fields +should be applied to capturing. If implemented, the following code examples +would become valid: + +```rust +let _a = &mut foo.a; +|| &mut foo.b; // Error! cannot borrow `foo` +``` + +```rust +let _a = &mut foo.a; +move || foo.b; // Error! cannot move `foo` +``` + +Note that there is open issue relating to this RFC +([#19004](https://github.com/rust-lang/rust/issues/19004)) where some discussion +has already taken place. + +# Motivation +[motivation]: #motivation + +In the rust language today, any variables named within a closure will be fully +captured. This was simple to implement but is inconstant with the rest of the +language because rust normally allows simultaneous borrowing of disjoint fields. +Remembering this exception adds to the mental burden of the programmer and +worsens rust's already terrible learning curve. + +The following is allowed; why should closures be treated differently? +```rust +let _a = &mut foo.a; +loop { &mut foo.b; } // ok! +``` + +This is a particularly annoying problem because closures often need to borrow +data from `self`: +```rust +pub fn update(&mut self) { + // cannot borrow `self` as immutable because `self.list` is also borrowed as mutable + self.list.retain(|i| self.filter.allowed(i)); +} +``` + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +The borrow checker understands structs sufficiently to know that it's possible +to borrow disjoint fields of a struct simultaneously. Structs can also be +destructed and moved piece-by-piece. This functionality should be available +anywhere, including from within closures: + +```rust +struct OneOf { + text: String, + of: Vec, +} + +impl OneOf { + pub fn matches(self) -> bool { + // Ok! destructure self + self.of.into_iter().any(|s| s == self.text) + } + + pub fn filter(&mut self) { + // Ok! mutate and inspect self + self.of.retain(|s| s != &self.text) + } +} +``` + +Rust will prevent dangerous double usage: + +```rust +struct FirstDuplicated(Vec) + +impl FirstDuplicated { + pub fn first_count(self) -> usize { + // Error! can't destructure and mutate same data + self.0.into_iter() + .filter(|s| &s == &self.0[0]) + .count() + } + + pub fn remove_first(&mut self) { + // Error! can't mutate and inspect same data + self.0.retain(|s| s != &self.0[0]) + } +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +There exists an nonoptimal but trivial desugar/workaround that covers all cases +via the following expansion: +``` +capture := [|'&'|'&mut '] ident ['.' ident]* + +'|' args '|' [$e($c:capture):expression]* => +'{' + ['let' $name:ident '=' $c ';']* + '|' args '|' [$e($name)]* +'}' +``` + +Applied to the first two examples: +```rust +let _a = &mut foo.a; +let b = &mut foo.b; +|| b; +``` +```rust +let _a = &mut foo.a; +let b = foo.b; +move || b; +``` + +This proves that the RFC can be safely implemented without violating any +existing assumptions. Also, because the compiler would become strictly more +lenient, it is nonbreaking. + +This RFC should not be implemented as such a desugar. Rather, the two following +changes might be made: +- Borrowck rules are altered so that capture-by-reference allows simultaneous + borrowing of disjoint fields. +- Codegen and borrowck are altered so that move closures capture only used + fields. + +## Examples of an ideal implementation + +```rust +|| &mut foo.a; +``` +- Borrowck passes because `foo` is not borrowed elsewhere. +- The closure captures a pointer to `foo.a`. + +```rust +let _a = &mut foo.a; +|| &mut foo.b; +``` +- Borrowck passes because `foo.a` and `foo.b` are disjoint. +- The closure captures a pointer to `foo.b`. + +```rust +let _a = &mut foo.a; +|| (&mut foo.b, &mut foo.c); +``` +- Borrowck passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. +- The closure captures a pointer to `foo`. + +```rust +move || foo.a; +``` +- Borrowck passes because `foo` is not borrowed elsewhere. +- The closure moves and captures `foo.a` but not `foo.b`. + +```rust +let _a = &mut foo.a; +move || foo.b; +``` +- Borrowck passes because `foo.a` and `foo.b` are disjoint. +- The closure moves and captures `foo.b`. + +# Drawbacks +[drawbacks]: #drawbacks + +This RFC does ruin the intuition that *all* variables named within a closure are +captured. I argue that that intuition is not really necessary, and killing it is +worth the cost. + +# Rationale and alternatives +[alternatives]: #alternatives + +This proposal is purely ergonomic since there is a complete and common +workaround. The existing rules could remain in place and rust users could +continue to pre-borrow/move fields. However, this workaround results in +significant boilerplate when borrowing many but not all of the fields in a +struct. It also produces a larger closure than necessary which could be the +difference between inlining and heap allocation. + +# Unresolved questions +[unresolved]: #unresolved-questions + +- How exactly does codegen change? +- Depending on implementation, captured pointers may no longer be exclusive, + careful with LLVM hints? +- Can borrowck be simplified as a result of this RFC? +- What unresolved questions do people have about this RFC? From c154b9030461476abbcef438322e0bca1dec7498 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Wed, 29 Nov 2017 20:13:42 -0700 Subject: [PATCH 02/10] capture disjoint drop case and rephrasing --- text/0000-capture-disjoint-fields.md | 71 +++++++++++++++++++++------- 1 file changed, 54 insertions(+), 17 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index 3b9b5e82db4..9d57460e6eb 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -21,9 +21,9 @@ let _a = &mut foo.a; move || foo.b; // Error! cannot move `foo` ``` -Note that there is open issue relating to this RFC -([#19004](https://github.com/rust-lang/rust/issues/19004)) where some discussion -has already taken place. +Note that some discussion of this has already taken place: +- rust-lang/rust#19004 +- [Rust internals forum](https://internals.rust-lang.org/t/borrow-the-full-stable-name-in-closures-for-ergonomics/5387) # Motivation [motivation]: #motivation @@ -31,10 +31,11 @@ has already taken place. In the rust language today, any variables named within a closure will be fully captured. This was simple to implement but is inconstant with the rest of the language because rust normally allows simultaneous borrowing of disjoint fields. -Remembering this exception adds to the mental burden of the programmer and -worsens rust's already terrible learning curve. +Remembering this exception adds to the mental burden of the programmer and makes +the rules of borrowing and ownership harder to learn. The following is allowed; why should closures be treated differently? + ```rust let _a = &mut foo.a; loop { &mut foo.b; } // ok! @@ -42,6 +43,7 @@ loop { &mut foo.b; } // ok! This is a particularly annoying problem because closures often need to borrow data from `self`: + ```rust pub fn update(&mut self) { // cannot borrow `self` as immutable because `self.list` is also borrowed as mutable @@ -101,6 +103,7 @@ impl FirstDuplicated { There exists an nonoptimal but trivial desugar/workaround that covers all cases via the following expansion: + ``` capture := [|'&'|'&mut '] ident ['.' ident]* @@ -112,11 +115,13 @@ capture := [|'&'|'&mut '] ident ['.' ident]* ``` Applied to the first two examples: + ```rust let _a = &mut foo.a; let b = &mut foo.b; || b; ``` + ```rust let _a = &mut foo.a; let b = foo.b; @@ -127,18 +132,28 @@ This proves that the RFC can be safely implemented without violating any existing assumptions. Also, because the compiler would become strictly more lenient, it is nonbreaking. -This RFC should not be implemented as such a desugar. Rather, the two following -changes might be made: +This RFC should *not* be implemented as such a desugar. Rather, the two +following changes might be made: + - Borrowck rules are altered so that capture-by-reference allows simultaneous - borrowing of disjoint fields. -- Codegen and borrowck are altered so that move closures capture only used - fields. + borrowing of disjoint fields. Field references are either individually + captured or all captured by a single nonexclusive pointer to the whole struct. +- Codegen and borrowck are altered so that move closures destructure and capture + only used fields when possible. This does require some minimal knowledge of + destructuring rules (types that implement `Drop` must be fully moved). + +The compiler should resolve captures recursively, always producing the minimal +capture even when encountering complex cases such as a `Drop` type inside a +destructurable type. ## Examples of an ideal implementation +Below are examples of how the compiler might idealy handle various captures: + ```rust || &mut foo.a; ``` + - Borrowck passes because `foo` is not borrowed elsewhere. - The closure captures a pointer to `foo.a`. @@ -146,6 +161,7 @@ changes might be made: let _a = &mut foo.a; || &mut foo.b; ``` + - Borrowck passes because `foo.a` and `foo.b` are disjoint. - The closure captures a pointer to `foo.b`. @@ -153,28 +169,49 @@ let _a = &mut foo.a; let _a = &mut foo.a; || (&mut foo.b, &mut foo.c); ``` + - Borrowck passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. - The closure captures a pointer to `foo`. ```rust move || foo.a; ``` + - Borrowck passes because `foo` is not borrowed elsewhere. -- The closure moves and captures `foo.a` but not `foo.b`. +- The closure moves and captures `foo.a` but not `foo.b` because `foo` can be + destructured. ```rust let _a = &mut foo.a; move || foo.b; ``` + - Borrowck passes because `foo.a` and `foo.b` are disjoint. -- The closure moves and captures `foo.b`. +- The closure moves and captures `foo.b` because `foo` can be destructured. + +```rust +move || drop_foo.a; +``` + +- Borrowck passes because no part of `drop_foo` is borrowed elsewhere. +- The closure moves and captures all of `drop_foo` because `drop_foo` implements + `Drop`. + +```rust +move || foo.drop_hello.a; +``` + +- Borrowck passes because `foo` and no part of `drop_hello` are borrowed + elsewhere. +- The closure moves and captures all of `foo.drop_hello` but not `foo.world` + because `drop_hello` implements `Drop` but `foo` does not. # Drawbacks [drawbacks]: #drawbacks This RFC does ruin the intuition that *all* variables named within a closure are -captured. I argue that that intuition is not really necessary, and killing it is -worth the cost. +captured. I argue that that intuition is not common or necessary enough to +justify the current approach. # Rationale and alternatives [alternatives]: #alternatives @@ -189,8 +226,8 @@ difference between inlining and heap allocation. # Unresolved questions [unresolved]: #unresolved-questions -- How exactly does codegen change? - Depending on implementation, captured pointers may no longer be exclusive, careful with LLVM hints? -- Can borrowck be simplified as a result of this RFC? -- What unresolved questions do people have about this RFC? +- Do non lexical lifetimes have any bearing on this particular inconvenience? +- Are detailed error messages required for complex cases (e.g. + `foo.drop_hello.a` being captured while `foo.drop_hello.b` is borrowed)? From 322a2e43245aff866832ac7f2a4628da99488544 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Thu, 7 Dec 2017 11:57:07 -0700 Subject: [PATCH 03/10] capture disjoint: mir question and error examples --- text/0000-capture-disjoint-fields.md | 45 ++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index 9d57460e6eb..337bca27f50 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -101,11 +101,11 @@ impl FirstDuplicated { # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -There exists an nonoptimal but trivial desugar/workaround that covers all cases -via the following expansion: +There exists an nonoptimal desugar/workaround that covers all cases via the +following expansion: ``` -capture := [|'&'|'&mut '] ident ['.' ident]* +capture := [|'&'|'&mut '] ident ['.' moveable_ident]* '|' args '|' [$e($c:capture):expression]* => '{' @@ -198,13 +198,37 @@ move || drop_foo.a; `Drop`. ```rust -move || foo.drop_hello.a; +let _hello = &foo.hello; +move || foo.drop_world.a; ``` -- Borrowck passes because `foo` and no part of `drop_hello` are borrowed - elsewhere. -- The closure moves and captures all of `foo.drop_hello` but not `foo.world` - because `drop_hello` implements `Drop` but `foo` does not. +- Borrowck passes because `foo.hello` and `foo.drop_world` are disjoint and no + part of `drop_world` is borrowed elsewhere. +- The closure moves and captures all of `foo.drop_world` but not `foo.hello` + because `drop_world` implements `Drop` but `foo` does not. + +## Example errors + +```rust +let _foo_again = &mut foo; +|| &mut foo.a; +``` + +- Borrowck fails because `_foo_again` and `foo.a` intersect. + +```rust +let _a = foo.a; +|| foo.a; +``` + +- Borrowck fails because `foo.a` has already been moved. + +```rust +let _a = drop_foo.a; +move || drop_foo.b; +``` + +- Borrowck fails because `drop_foo` can not be destructured. # Drawbacks [drawbacks]: #drawbacks @@ -228,6 +252,9 @@ difference between inlining and heap allocation. - Depending on implementation, captured pointers may no longer be exclusive, careful with LLVM hints? +- How can safe MIR be generated when capturing non-exclusive pointers + (dereferenced disjointly)? + - Use refinement typing? - Do non lexical lifetimes have any bearing on this particular inconvenience? - Are detailed error messages required for complex cases (e.g. - `foo.drop_hello.a` being captured while `foo.drop_hello.b` is borrowed)? + `foo.drop_world.a` being captured while `foo.drop_world.b` is borrowed)? From b7bb50daca311aae7d200435cd8dfdb9fe4ef333 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Mon, 14 May 2018 09:59:06 -0600 Subject: [PATCH 04/10] begin rewriting --- text/0000-capture-disjoint-fields.md | 54 ++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index 337bca27f50..c6c79bb5f51 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -7,7 +7,7 @@ [summary]: #summary This RFC proposes that closure capturing should be minimal rather than maximal. -Specifically, existing rules regarding borrowing and moving disjoint fields +Conceptually, existing rules regarding borrowing and moving disjoint fields should be applied to capturing. If implemented, the following code examples would become valid: @@ -54,7 +54,7 @@ pub fn update(&mut self) { # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -The borrow checker understands structs sufficiently to know that it's possible +Rust understands structs sufficiently to know that it's possible to borrow disjoint fields of a struct simultaneously. Structs can also be destructed and moved piece-by-piece. This functionality should be available anywhere, including from within closures: @@ -101,6 +101,56 @@ impl FirstDuplicated { # Reference-level explanation [reference-level-explanation]: #reference-level-explanation +This RFC does not propose any changes to borrowck. Instead, the MIR generation +for closures should be altered to produce the minimal capture. Additionally, a +hidden `repr` for closures might be added, which could reduce closure size +through awareness of the new capture rules *(see unresolved)*. + +A capture is a list of expressions, which we will call "capture items". These +expressions are parts of the closure body that will be pre-evaluated when the +closure is created. + +If the closure is `move`: + +- For each binding used in the closure, add a capture item which moves the +value of that binding. + +Otherwise: + + +- For each binding used in the closure, add a capture item which borrows the +value of that binding. The mutability of this borrow must be inferred from the +closure body. + +Next, the capture list is iterativly narrowed. When an item is narrowed, it is +removed from this list, but at least one new item is generated. Remember that +each capture item is some pre-evaluatable part of the closure body. Narrowing +stops when all items can not be narrowed further. + +Any field of a captured struct is considered "used" if the body of the closure +requires its value. + +Any item that evaluates to borrowed struct can be narrowed so long as narrowing +does not produce any expression that additionally calls a function or +overloaded deref. When a borrowed struct is narrowed, new items are generated +which similarly borrow each used field. + +Any item that evaluates to an owned struct can be narrowed so long as the +struct does not implement `Drop`. Additionally, we might forbid narrowing if +the struct contains unused fields that implement `Drop`. This will prevent the +drop order of those fields from changing, but feels strange and non-orthogonal +*(see unresolved)*. Encountering this case at all could trigger a warning, so +that this extra rule could exist but be removed over an epoc *(see +unresolved)*. When an owned struct is narrowed, new items are generated which +move each used field. + +It has also been proposed that any item `x`, which evaluates to `Box` can be +narrowed to `Deref::deref(x)`. *(see unresolved)*. Potentially this could be +generalized over some concept of a "pure" deref, as marked by some new +annotation or trait *(see unresolved)*. + + + There exists an nonoptimal desugar/workaround that covers all cases via the following expansion: From 52c6eb4a1b7f326ede7f601017b9e76fe3c73c51 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Mon, 21 May 2018 20:58:10 -0600 Subject: [PATCH 05/10] major rewrite of reference section complete --- text/0000-capture-disjoint-fields.md | 258 ++++++++++++--------------- 1 file changed, 116 insertions(+), 142 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index c6c79bb5f51..62db6011898 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -29,10 +29,10 @@ Note that some discussion of this has already taken place: [motivation]: #motivation In the rust language today, any variables named within a closure will be fully -captured. This was simple to implement but is inconstant with the rest of the -language because rust normally allows simultaneous borrowing of disjoint fields. -Remembering this exception adds to the mental burden of the programmer and makes -the rules of borrowing and ownership harder to learn. +captured. This was simple to implement but is inconsistent with the rest of the +language because rust normally allows simultaneous borrowing of disjoint +fields. Remembering this exception adds to the mental burden of the programmer +and makes the rules of borrowing and ownership harder to learn. The following is allowed; why should closures be treated differently? @@ -106,186 +106,149 @@ for closures should be altered to produce the minimal capture. Additionally, a hidden `repr` for closures might be added, which could reduce closure size through awareness of the new capture rules *(see unresolved)*. -A capture is a list of expressions, which we will call "capture items". These -expressions are parts of the closure body that will be pre-evaluated when the -closure is created. - -If the closure is `move`: - -- For each binding used in the closure, add a capture item which moves the -value of that binding. - -Otherwise: - - -- For each binding used in the closure, add a capture item which borrows the -value of that binding. The mutability of this borrow must be inferred from the -closure body. - -Next, the capture list is iterativly narrowed. When an item is narrowed, it is -removed from this list, but at least one new item is generated. Remember that -each capture item is some pre-evaluatable part of the closure body. Narrowing -stops when all items can not be narrowed further. - -Any field of a captured struct is considered "used" if the body of the closure -requires its value. - -Any item that evaluates to borrowed struct can be narrowed so long as narrowing -does not produce any expression that additionally calls a function or -overloaded deref. When a borrowed struct is narrowed, new items are generated -which similarly borrow each used field. - -Any item that evaluates to an owned struct can be narrowed so long as the -struct does not implement `Drop`. Additionally, we might forbid narrowing if -the struct contains unused fields that implement `Drop`. This will prevent the -drop order of those fields from changing, but feels strange and non-orthogonal -*(see unresolved)*. Encountering this case at all could trigger a warning, so -that this extra rule could exist but be removed over an epoc *(see -unresolved)*. When an owned struct is narrowed, new items are generated which -move each used field. - -It has also been proposed that any item `x`, which evaluates to `Box` can be -narrowed to `Deref::deref(x)`. *(see unresolved)*. Potentially this could be -generalized over some concept of a "pure" deref, as marked by some new -annotation or trait *(see unresolved)*. - - - -There exists an nonoptimal desugar/workaround that covers all cases via the -following expansion: - -``` -capture := [|'&'|'&mut '] ident ['.' moveable_ident]* - -'|' args '|' [$e($c:capture):expression]* => -'{' - ['let' $name:ident '=' $c ';']* - '|' args '|' [$e($name)]* -'}' -``` - -Applied to the first two examples: +In a sense, when a closure is lowered to MIR, a list of "capture expressions" +is created, which we will call the "capture set". Each expression is some part +of the closure body which, in order to capture parts of the enclosing scope, +must be pre-evaluated when the closure is created. The output of the +expressions, which we will call "capture data", is stored in the anonymous +struct which implements the `Fn*` traits. If a binding is used within a +closure, at least one capture expression which borrows or moves that binding's +value must exist in the capture set. + +Currently, lowering creates exactly one capture expression for each used +binding, which borrows or moves the value in its entirety. This RFC proposes +that lowering should instead create the minimal capture, where each expression +is as specific as possible. + +This minimal set of capture expressions *might* be created by starting with the +existing capture set (one maximal expression per binding) and then iterativly +modifying and splitting the expressions by adding additional dereferences and +path components. + +A capture expression is minimal if it produces... + +- a value that is not a struct or borrowed struct (e.g. primitive, enum, union). +- a value that is used by the closure in its entirety (e.g. passed outside the closure). +- any other case where the expression can not be made more specific (see below). + +Additionally capture expressions are not allowed to... + +- call impure functions. +- make illegal moves (for example, out of a `Drop` type). +- *(comments please, am I missing any cases?)*. + +Note that *all* functions are considered impure (including to overloaded deref +impls). And, for the sake of capturing, all indexing is considered impure *(see +unresolved)*. It is possible that overloaded `Deref::deref` implementations +could be marked as pure by using a new, unsafe marker trait (such as +`DerefPure`) or attribute (such as `#[deref_transparent]`). In the meantime, +`::deref` could be a special case of a pure function *(see +unresolved)*. + +Note that, because capture expressions are all subsets of the closure body, +this RFC does not change *what* is executed. It does change the order/number of +executions for some operations, but since these must be pure, order/repetition +does not matter. Only changes to lifetimes might be breaking. Specifically, the +drop order of uncaptured data can be altered. + +We might solve this by considering a struct to be minimal if it contains unused +fields that implement `Drop`. This would prevent the drop order of those fields +from changing, but feels strange and non-orthogonal *(see unresolved)*. +Encountering this case at all could trigger a warning, so that this extra rule +could exist temporarily but be removed over the next epoc *(see unresolved)*. + +## Reference Examples + +Below are examples of various closures and their capture sets. ```rust -let _a = &mut foo.a; -let b = &mut foo.b; -|| b; +let foo = 10; +|| &mut foo; ``` +- `&mut foo` (not a struct) + ```rust let _a = &mut foo.a; -let b = foo.b; -move || b; +|| (&mut foo.b, &mut foo.c); ``` -This proves that the RFC can be safely implemented without violating any -existing assumptions. Also, because the compiler would become strictly more -lenient, it is nonbreaking. +- `&mut foo.b` (used in entirety) +- `&mut foo.a` (used in entirety) -This RFC should *not* be implemented as such a desugar. Rather, the two -following changes might be made: - -- Borrowck rules are altered so that capture-by-reference allows simultaneous - borrowing of disjoint fields. Field references are either individually - captured or all captured by a single nonexclusive pointer to the whole struct. -- Codegen and borrowck are altered so that move closures destructure and capture - only used fields when possible. This does require some minimal knowledge of - destructuring rules (types that implement `Drop` must be fully moved). - -The compiler should resolve captures recursively, always producing the minimal -capture even when encountering complex cases such as a `Drop` type inside a -destructurable type. - -## Examples of an ideal implementation - -Below are examples of how the compiler might idealy handle various captures: - -```rust -|| &mut foo.a; -``` - -- Borrowck passes because `foo` is not borrowed elsewhere. -- The closure captures a pointer to `foo.a`. +Borrowck passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. ```rust let _a = &mut foo.a; -|| &mut foo.b; +move || foo.b; ``` -- Borrowck passes because `foo.a` and `foo.b` are disjoint. -- The closure captures a pointer to `foo.b`. +- `foo.b` (used in entirety) -```rust -let _a = &mut foo.a; -|| (&mut foo.b, &mut foo.c); -``` - -- Borrowck passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. -- The closure captures a pointer to `foo`. +Borrowck passes because `foo.a` and `foo.b` are disjoint. ```rust -move || foo.a; +let _hello = &foo.hello; +move || foo.drop_world.a; ``` -- Borrowck passes because `foo` is not borrowed elsewhere. -- The closure moves and captures `foo.a` but not `foo.b` because `foo` can be - destructured. +- `foo.drop_world` (owned & implements drop) + +Borrowck passes because `foo.hello` and `foo.drop_world` are disjoint. ```rust -let _a = &mut foo.a; -move || foo.b; +|| println!("{}", foo.wrapper_thing.a); ``` -- Borrowck passes because `foo.a` and `foo.b` are disjoint. -- The closure moves and captures `foo.b` because `foo` can be destructured. +- `&foo.wrapper_thing` (overloaded `Deref` on `wrapper_thing` is impure) ```rust -move || drop_foo.a; +|| foo.list[0]; ``` -- Borrowck passes because no part of `drop_foo` is borrowed elsewhere. -- The closure moves and captures all of `drop_foo` because `drop_foo` implements - `Drop`. +- `foo.list` (indexing is impure) ```rust -let _hello = &foo.hello; -move || foo.drop_world.a; +let bar = (1, 2); // struct +|| myfunc(bar); ``` -- Borrowck passes because `foo.hello` and `foo.drop_world` are disjoint and no - part of `drop_world` is borrowed elsewhere. -- The closure moves and captures all of `foo.drop_world` but not `foo.hello` - because `drop_world` implements `Drop` but `foo` does not. - -## Example errors +- `bar` (used in entirety) ```rust let _foo_again = &mut foo; || &mut foo.a; ``` -- Borrowck fails because `_foo_again` and `foo.a` intersect. +- `&mut foo.a` (used in entirety) + +Borrowck fails because `_foo_again` and `foo.a` intersect. ```rust let _a = foo.a; || foo.a; ``` -- Borrowck fails because `foo.a` has already been moved. +- `foo.a` (used in entirety) + +Borrowck fails because `foo.a` has already been moved. ```rust let _a = drop_foo.a; move || drop_foo.b; ``` -- Borrowck fails because `drop_foo` can not be destructured. +- `drop_foo` (owned & implements drop) + +Borrowck fails because `drop_foo` can not be destructured + use of partially +moved value. # Drawbacks [drawbacks]: #drawbacks -This RFC does ruin the intuition that *all* variables named within a closure are -captured. I argue that that intuition is not common or necessary enough to -justify the current approach. +This RFC does ruin the intuition that *all* variables named within a closure +are captured. I argue that that intuition is not common or necessary enough to +justify the extra glue code. # Rationale and alternatives [alternatives]: #alternatives @@ -293,18 +256,29 @@ justify the current approach. This proposal is purely ergonomic since there is a complete and common workaround. The existing rules could remain in place and rust users could continue to pre-borrow/move fields. However, this workaround results in -significant boilerplate when borrowing many but not all of the fields in a -struct. It also produces a larger closure than necessary which could be the -difference between inlining and heap allocation. +significant useless glue code when borrowing many but not all of the fields in +a struct. It also produces a larger closure than necessary which could make the +difference when inlining. # Unresolved questions [unresolved]: #unresolved-questions -- Depending on implementation, captured pointers may no longer be exclusive, - careful with LLVM hints? -- How can safe MIR be generated when capturing non-exclusive pointers - (dereferenced disjointly)? - - Use refinement typing? -- Do non lexical lifetimes have any bearing on this particular inconvenience? -- Are detailed error messages required for complex cases (e.g. - `foo.drop_world.a` being captured while `foo.drop_world.b` is borrowed)? +- How to optimize pointers. Can borrows that all reference parts of the same +object be stored as a single pointer? How should this optimization be +implemented (e.g. a special `repr`, refinement typing)? + +- Any reason for non-overloaded index-by-constant to be pre-evaluated? It is +technically pure. Could this be left as an implementation/optimization +decision? + +- How to signal that a function is pure. Is this even needed/wanted? Any other +places where the language could benefit? + +- Should `Box` be special? + +- Drop order can change as a result of this RFC, is this a real stability +problem? It is hard to imagine this breaking anything in the rust ecosystem. + +- Would lifetime changes be more breaking after NLL is stabilized? + +- How to avoid breaking changes if needed. From f366f92abaca37cb7df9cb312b4341416073bf75 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Wed, 25 Jul 2018 12:50:56 -0600 Subject: [PATCH 06/10] fixed a few nits and borrowck usage --- text/0000-capture-disjoint-fields.md | 60 +++++++++++++--------------- 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index 62db6011898..941efd79ccb 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -101,19 +101,19 @@ impl FirstDuplicated { # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This RFC does not propose any changes to borrowck. Instead, the MIR generation -for closures should be altered to produce the minimal capture. Additionally, a -hidden `repr` for closures might be added, which could reduce closure size -through awareness of the new capture rules *(see unresolved)*. - -In a sense, when a closure is lowered to MIR, a list of "capture expressions" -is created, which we will call the "capture set". Each expression is some part -of the closure body which, in order to capture parts of the enclosing scope, -must be pre-evaluated when the closure is created. The output of the -expressions, which we will call "capture data", is stored in the anonymous -struct which implements the `Fn*` traits. If a binding is used within a -closure, at least one capture expression which borrows or moves that binding's -value must exist in the capture set. +This RFC does not propose any changes to the borrow checker. Instead, the MIR +generation for closures should be altered to produce the minimal capture. +Additionally, a hidden `repr` for closures might be added, which could reduce +closure size through awareness of the new capture rules *(see unresolved)*. + +In a sense, when a closure is lowered to MIR, a list of "capture expressions" is +created, which we will call the "capture set". Each expression is some part of +the closure body which, in order to capture parts of the enclosing scope, must +be pre-evaluated when the closure is created. The output of the expressions, +which we will call "capture data", is stored in the anonymous struct which +implements the `Fn*` traits. If a binding is used within a closure, at least one +capture expression which borrows or moves that binding's value must exist in the +capture set. Currently, lowering creates exactly one capture expression for each used binding, which borrows or moves the value in its entirety. This RFC proposes @@ -140,10 +140,10 @@ Additionally capture expressions are not allowed to... Note that *all* functions are considered impure (including to overloaded deref impls). And, for the sake of capturing, all indexing is considered impure *(see unresolved)*. It is possible that overloaded `Deref::deref` implementations -could be marked as pure by using a new, unsafe marker trait (such as -`DerefPure`) or attribute (such as `#[deref_transparent]`). In the meantime, -`::deref` could be a special case of a pure function *(see -unresolved)*. +could be marked as pure by using a new, marker trait (such as `DerefPure`) or +attribute (such as `#[deref_transparent]`). However, such a solution should be +proposed in a separate RFC. In the meantime, `::deref` could be a +special case of a pure function *(see unresolved)*. Note that, because capture expressions are all subsets of the closure body, this RFC does not change *what* is executed. It does change the order/number of @@ -176,7 +176,7 @@ let _a = &mut foo.a; - `&mut foo.b` (used in entirety) - `&mut foo.a` (used in entirety) -Borrowck passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. +The borrow checker passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. ```rust let _a = &mut foo.a; @@ -185,7 +185,7 @@ move || foo.b; - `foo.b` (used in entirety) -Borrowck passes because `foo.a` and `foo.b` are disjoint. +The borrow checker passes because `foo.a` and `foo.b` are disjoint. ```rust let _hello = &foo.hello; @@ -194,7 +194,7 @@ move || foo.drop_world.a; - `foo.drop_world` (owned & implements drop) -Borrowck passes because `foo.hello` and `foo.drop_world` are disjoint. +The borrow checker passes because `foo.hello` and `foo.drop_world` are disjoint. ```rust || println!("{}", foo.wrapper_thing.a); @@ -222,7 +222,7 @@ let _foo_again = &mut foo; - `&mut foo.a` (used in entirety) -Borrowck fails because `_foo_again` and `foo.a` intersect. +The borrow checker fails because `_foo_again` and `foo.a` intersect. ```rust let _a = foo.a; @@ -231,7 +231,7 @@ let _a = foo.a; - `foo.a` (used in entirety) -Borrowck fails because `foo.a` has already been moved. +The borrow checker fails because `foo.a` has already been moved. ```rust let _a = drop_foo.a; @@ -240,15 +240,15 @@ move || drop_foo.b; - `drop_foo` (owned & implements drop) -Borrowck fails because `drop_foo` can not be destructured + use of partially -moved value. +The borrow checker fails because `drop_foo` can not be destructured + use of +partially moved value. # Drawbacks [drawbacks]: #drawbacks -This RFC does ruin the intuition that *all* variables named within a closure -are captured. I argue that that intuition is not common or necessary enough to -justify the extra glue code. +This RFC does ruin the intuition that all variables named within a closure are +*completely* captured. I argue that that intuition is not common or necessary +enough to justify the extra glue code. # Rationale and alternatives [alternatives]: #alternatives @@ -277,8 +277,4 @@ places where the language could benefit? - Should `Box` be special? - Drop order can change as a result of this RFC, is this a real stability -problem? It is hard to imagine this breaking anything in the rust ecosystem. - -- Would lifetime changes be more breaking after NLL is stabilized? - -- How to avoid breaking changes if needed. + problem? How should this be resolved? From 15f2eee421a1232f6c40664c6f964c555c015c42 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Fri, 27 Jul 2018 16:34:12 -0600 Subject: [PATCH 07/10] reference examples: add box, nll fixes, and typos --- text/0000-capture-disjoint-fields.md | 39 ++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index 941efd79ccb..318ba4c8789 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -169,18 +169,20 @@ let foo = 10; - `&mut foo` (not a struct) ```rust -let _a = &mut foo.a; +let a = &mut foo.a; || (&mut foo.b, &mut foo.c); +somefunc(a); ``` - `&mut foo.b` (used in entirety) -- `&mut foo.a` (used in entirety) +- `&mut foo.c` (used in entirety) The borrow checker passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. ```rust -let _a = &mut foo.a; +let a = &mut foo.a; move || foo.b; +somefunc(a); ``` - `foo.b` (used in entirety) @@ -188,11 +190,12 @@ move || foo.b; The borrow checker passes because `foo.a` and `foo.b` are disjoint. ```rust -let _hello = &foo.hello; +let hello = &foo.hello; move || foo.drop_world.a; +somefunc(hello); ``` -- `foo.drop_world` (owned & implements drop) +- `foo.drop_world` (owned and implements drop) The borrow checker passes because `foo.hello` and `foo.drop_world` are disjoint. @@ -216,13 +219,14 @@ let bar = (1, 2); // struct - `bar` (used in entirety) ```rust -let _foo_again = &mut foo; +let foo_again = &mut foo; || &mut foo.a; +somefunc(foo_again); ``` - `&mut foo.a` (used in entirety) -The borrow checker fails because `_foo_again` and `foo.a` intersect. +The borrow checker fails because `foo_again` and `foo.a` intersect. ```rust let _a = foo.a; @@ -234,14 +238,27 @@ let _a = foo.a; The borrow checker fails because `foo.a` has already been moved. ```rust -let _a = drop_foo.a; +let a = &drop_foo.a; move || drop_foo.b; +somefunc(a); +``` + +- `drop_foo` (owned and implements drop) + +The borrow checker fails because `drop_foo` can not be moved while borrowed. + +```rust +|| &box_foo.a; ``` -- `drop_foo` (owned & implements drop) +- `& as Deref>::deref(&box_foo).b` (pure function call) + +```rust +move || &box_foo.a; +``` -The borrow checker fails because `drop_foo` can not be destructured + use of -partially moved value. +- `box_foo` (`move` forces full capure of `box_foo`, since it can not be + destructured) # Drawbacks [drawbacks]: #drawbacks From 4c8f1aee8c5b28d22ed805819b865052c0e4280c Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Tue, 7 Aug 2018 16:49:01 -0600 Subject: [PATCH 08/10] attempt to clarify behavior of move --- text/0000-capture-disjoint-fields.md | 63 +++++++++++++++++----------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index 318ba4c8789..8e5353274f7 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -125,17 +125,18 @@ existing capture set (one maximal expression per binding) and then iterativly modifying and splitting the expressions by adding additional dereferences and path components. -A capture expression is minimal if it produces... +A capture expression is minimal if it produces a value that is used by the +closure in its entirety (e.g. is a primitive, is passed outside the closure, +etc.) or if making the expression more specific would require one the following. -- a value that is not a struct or borrowed struct (e.g. primitive, enum, union). -- a value that is used by the closure in its entirety (e.g. passed outside the closure). -- any other case where the expression can not be made more specific (see below). +- a call to an impure function +- an illegal move (for example, out of a `Drop` type) -Additionally capture expressions are not allowed to... - -- call impure functions. -- make illegal moves (for example, out of a `Drop` type). -- *(comments please, am I missing any cases?)*. +When generating a capture expression, we must decide if the output should be +owned or if it can be a reference. In a non-`move` closure, a capture expression +will *only* produce owned data if ownership of that data is required by the body +of the closure. In a `move` closure, will *always* produced owned data unless +the captured binding does not have ownership. Note that *all* functions are considered impure (including to overloaded deref impls). And, for the sake of capturing, all indexing is considered impure *(see @@ -145,7 +146,7 @@ attribute (such as `#[deref_transparent]`). However, such a solution should be proposed in a separate RFC. In the meantime, `::deref` could be a special case of a pure function *(see unresolved)*. -Note that, because capture expressions are all subsets of the closure body, +Also note that, because capture expressions are all subsets of the closure body, this RFC does not change *what* is executed. It does change the order/number of executions for some operations, but since these must be pure, order/repetition does not matter. Only changes to lifetimes might be breaking. Specifically, the @@ -166,7 +167,7 @@ let foo = 10; || &mut foo; ``` -- `&mut foo` (not a struct) +- `&mut foo` (primitive, ownership not required, used in entirety) ```rust let a = &mut foo.a; @@ -174,8 +175,8 @@ let a = &mut foo.a; somefunc(a); ``` -- `&mut foo.b` (used in entirety) -- `&mut foo.c` (used in entirety) +- `&mut foo.b` (ownership not required, used in entirety) +- `&mut foo.c` (ownership not required, used in entirety) The borrow checker passes because `foo.a`, `foo.b`, and `foo.c` are disjoint. @@ -185,7 +186,7 @@ move || foo.b; somefunc(a); ``` -- `foo.b` (used in entirety) +- `foo.b` (ownership available, used in entirety) The borrow checker passes because `foo.a` and `foo.b` are disjoint. @@ -195,7 +196,8 @@ move || foo.drop_world.a; somefunc(hello); ``` -- `foo.drop_world` (owned and implements drop) +- `foo.drop_world` (ownership available, can't be more specific without moving + out of `Drop`) The borrow checker passes because `foo.hello` and `foo.drop_world` are disjoint. @@ -203,20 +205,22 @@ The borrow checker passes because `foo.hello` and `foo.drop_world` are disjoint. || println!("{}", foo.wrapper_thing.a); ``` -- `&foo.wrapper_thing` (overloaded `Deref` on `wrapper_thing` is impure) +- `&foo.wrapper_thing` (ownership not required, can't be more specific because + overloaded `Deref` on `wrapper_thing` is impure) ```rust || foo.list[0]; ``` -- `foo.list` (indexing is impure) +- `foo.list` (ownership required, can't be more specific because indexing is + impure) ```rust let bar = (1, 2); // struct || myfunc(bar); ``` -- `bar` (used in entirety) +- `bar` (ownership required, used in entirety) ```rust let foo_again = &mut foo; @@ -224,7 +228,7 @@ let foo_again = &mut foo; somefunc(foo_again); ``` -- `&mut foo.a` (used in entirety) +- `&mut foo.a` (ownership not required, used in entirety) The borrow checker fails because `foo_again` and `foo.a` intersect. @@ -233,7 +237,7 @@ let _a = foo.a; || foo.a; ``` -- `foo.a` (used in entirety) +- `foo.a` (ownership required, used in entirety) The borrow checker fails because `foo.a` has already been moved. @@ -243,7 +247,8 @@ move || drop_foo.b; somefunc(a); ``` -- `drop_foo` (owned and implements drop) +- `drop_foo` (ownership available, can't be more specific without moving out of + `Drop`) The borrow checker fails because `drop_foo` can not be moved while borrowed. @@ -251,14 +256,24 @@ The borrow checker fails because `drop_foo` can not be moved while borrowed. || &box_foo.a; ``` -- `& as Deref>::deref(&box_foo).b` (pure function call) +- `& as Deref>::deref(&box_foo).b` (ownership not required, `Box::deref` is pure) ```rust move || &box_foo.a; ``` -- `box_foo` (`move` forces full capure of `box_foo`, since it can not be - destructured) +- `box_foo` (ownership available, can't be more specific without moving out of + `Drop`) + +```rust +let foo = &mut a; +let other = &mut foo.other; +move || &mut foo.bar; +somefunc(other); +``` + +- `&mut foo.bar` (ownership *not* available, borrow can be split) + # Drawbacks [drawbacks]: #drawbacks From d30f678d2e1336f89356f0793dfed857b9a40a27 Mon Sep 17 00:00:00 2001 From: Sam Sartor Date: Fri, 10 Aug 2018 16:46:51 -0600 Subject: [PATCH 09/10] final phrasing revisions --- text/0000-capture-disjoint-fields.md | 58 ++++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/text/0000-capture-disjoint-fields.md b/text/0000-capture-disjoint-fields.md index 8e5353274f7..a1a13843a3a 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/0000-capture-disjoint-fields.md @@ -12,13 +12,15 @@ should be applied to capturing. If implemented, the following code examples would become valid: ```rust -let _a = &mut foo.a; +let a = &mut foo.a; || &mut foo.b; // Error! cannot borrow `foo` +somefunc(a); ``` ```rust -let _a = &mut foo.a; +let a = &mut foo.a; move || foo.b; // Error! cannot move `foo` +somefunc(a); ``` Note that some discussion of this has already taken place: @@ -118,16 +120,18 @@ capture set. Currently, lowering creates exactly one capture expression for each used binding, which borrows or moves the value in its entirety. This RFC proposes that lowering should instead create the minimal capture, where each expression -is as specific as possible. +is as precise as possible. -This minimal set of capture expressions *might* be created by starting with the -existing capture set (one maximal expression per binding) and then iterativly -modifying and splitting the expressions by adding additional dereferences and -path components. +This minimal set of capture expressions *might* be created through a sort of +iterative refinement. We would start out capturing all of the local variables. +Then, each path would be made more precise by adding additional dereferences and +path components depending on which paths are used and how. References to structs +would be made more precise by reborrowing fields and owned structs would be made +more precise by moving fields. A capture expression is minimal if it produces a value that is used by the closure in its entirety (e.g. is a primitive, is passed outside the closure, -etc.) or if making the expression more specific would require one the following. +etc.) or if making the expression more precise would require one the following. - a call to an impure function - an illegal move (for example, out of a `Drop` type) @@ -135,16 +139,16 @@ etc.) or if making the expression more specific would require one the following. When generating a capture expression, we must decide if the output should be owned or if it can be a reference. In a non-`move` closure, a capture expression will *only* produce owned data if ownership of that data is required by the body -of the closure. In a `move` closure, will *always* produced owned data unless -the captured binding does not have ownership. +of the closure. A `move` closure will *always* produce owned data unless the +captured binding does not have ownership. Note that *all* functions are considered impure (including to overloaded deref -impls). And, for the sake of capturing, all indexing is considered impure *(see -unresolved)*. It is possible that overloaded `Deref::deref` implementations -could be marked as pure by using a new, marker trait (such as `DerefPure`) or -attribute (such as `#[deref_transparent]`). However, such a solution should be -proposed in a separate RFC. In the meantime, `::deref` could be a -special case of a pure function *(see unresolved)*. +implementations). And, for the sake of capturing, all indexing is considered +impure. It is possible that overloaded `Deref::deref` implementations could be +marked as pure by using a new, marker trait (such as `DerefPure`) or attribute +(such as `#[deref_transparent]`). However, such a solution should be proposed in +a separate RFC. In the meantime, `::deref` could be a special case +of a pure function *(see unresolved)*. Also note that, because capture expressions are all subsets of the closure body, this RFC does not change *what* is executed. It does change the order/number of @@ -196,7 +200,7 @@ move || foo.drop_world.a; somefunc(hello); ``` -- `foo.drop_world` (ownership available, can't be more specific without moving +- `foo.drop_world` (ownership available, can't be more precise without moving out of `Drop`) The borrow checker passes because `foo.hello` and `foo.drop_world` are disjoint. @@ -205,14 +209,14 @@ The borrow checker passes because `foo.hello` and `foo.drop_world` are disjoint. || println!("{}", foo.wrapper_thing.a); ``` -- `&foo.wrapper_thing` (ownership not required, can't be more specific because +- `&foo.wrapper_thing` (ownership not required, can't be more precise because overloaded `Deref` on `wrapper_thing` is impure) ```rust || foo.list[0]; ``` -- `foo.list` (ownership required, can't be more specific because indexing is +- `foo.list` (ownership required, can't be more precise because indexing is impure) ```rust @@ -247,10 +251,10 @@ move || drop_foo.b; somefunc(a); ``` -- `drop_foo` (ownership available, can't be more specific without moving out of +- `drop_foo` (ownership available, can't be more precise without moving out of `Drop`) -The borrow checker fails because `drop_foo` can not be moved while borrowed. +The borrow checker fails because `drop_foo` cannot be moved while borrowed. ```rust || &box_foo.a; @@ -262,7 +266,7 @@ The borrow checker fails because `drop_foo` can not be moved while borrowed. move || &box_foo.a; ``` -- `box_foo` (ownership available, can't be more specific without moving out of +- `box_foo` (ownership available, can't be more precise without moving out of `Drop`) ```rust @@ -296,15 +300,11 @@ difference when inlining. [unresolved]: #unresolved-questions - How to optimize pointers. Can borrows that all reference parts of the same -object be stored as a single pointer? How should this optimization be -implemented (e.g. a special `repr`, refinement typing)? - -- Any reason for non-overloaded index-by-constant to be pre-evaluated? It is -technically pure. Could this be left as an implementation/optimization -decision? + object be stored as a single pointer? How should this optimization be + implemented (e.g. a special `repr`, refinement typing)? - How to signal that a function is pure. Is this even needed/wanted? Any other -places where the language could benefit? + places where the language could benefit? - Should `Box` be special? From 395ced487f41f87dc0d442fe65f31f7abc9dac2c Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Sun, 19 Aug 2018 10:57:30 +0200 Subject: [PATCH 10/10] RFC 2229 --- ...e-disjoint-fields.md => 2229-capture-disjoint-fields.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename text/{0000-capture-disjoint-fields.md => 2229-capture-disjoint-fields.md} (97%) diff --git a/text/0000-capture-disjoint-fields.md b/text/2229-capture-disjoint-fields.md similarity index 97% rename from text/0000-capture-disjoint-fields.md rename to text/2229-capture-disjoint-fields.md index a1a13843a3a..b9700af4fed 100644 --- a/text/0000-capture-disjoint-fields.md +++ b/text/2229-capture-disjoint-fields.md @@ -1,7 +1,7 @@ -- Feature Name: capture-disjoint-fields +- Feature Name: `capture_disjoint_fields` - Start Date: 2017-11-28 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) +- RFC PR: [rust-lang/rfcs#2229](https://github.com/rust-lang/rfcs/pull/2229) +- Rust Issue: [rust-lang/rust#53488](https://github.com/rust-lang/rust/issues/53488) # Summary [summary]: #summary