From 7fa73071ae5ac0330c3cc6ada7b4f70835c0d8b7 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Wed, 30 Sep 2015 15:04:29 -0700 Subject: [PATCH 01/15] First draft of if-not-let RFC --- text/0000-if-not-let.md | 153 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 text/0000-if-not-let.md diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md new file mode 100644 index 00000000000..f8592236df9 --- /dev/null +++ b/text/0000-if-not-let.md @@ -0,0 +1,153 @@ +- Feature Name: if-not-let expression +- Start Date: 2015-09-30 +- RFC PR: +- Rust Issue: + +# Summary + +Introduce a new `if !let PAT = EXPR { BODY }` construct (informally called an +**if-not-let expression**). This works much like an if-let expression, but +executes its body when pattern matching fails. + +This narrows the gap between regular `if` expressions and `if let` +expressions, while also providing a simpler syntax for some common +error-handling patterns. + +# Motivation + +[if-let expressions][if-let] offer a succinct syntax for pattern matching +with only one "success" path. This is particularly useful for unwrapping +types like `Option`. However, an if-let expression can only create bindings +within its body, which can force rightward drift and excessive nesting. + +`if !let` is a logical extension of `if let` that moves the failure case into +the body, and allows the success case to follow without extra nesting. + +For example, this code written with current Rust syntax: + +```rust +if let Some(a) = x { + if let Some(b) = y { + if let Some(c) = z { + /* + * do something with a, b, and c + */ + } else { + return Err("bad z"); + } + } else { + return Err("bad y"); + } +} else { + return Err("bad x"); +} +``` + +would become: + +```rust +if !let Some(a) = x { + return Err("bad x"); +} +if !let Some(b) = y { + return Err("bad y"); +} +if !let Some(c) = z { + return Err("bad z"); +} +/* + * do something with a, b, and c + */ +``` + +It's possible to use `match` statements to emulate this today, but at a +significant cost in length and readability. For example, this real-world code +from Servo: + +```rust +let subpage_layer_info = match layer_properties.subpage_layer_info { + Some(ref subpage_layer_info) => *subpage_layer_info, + None => return, +}; +``` + +is equivalent to this much simpler if-not-let expression: + +```rust +if !let Some(subpage_layer_info) = match layer_properties.subpage_layer_info { + return +} +``` + +The Swift programming language, which inspired Rust's if-let expression, also +includes [guard-let-else][swift] expressions which are equivalent to this +proposal except for the choice of keywords. + +# Detailed design + +Extend the Rust expression grammar to include the following production: + +``` +expr_if_not_let = 'if' '!' 'let' pat '=' expr block +``` + +The pattern must be refutable. The body of the if-not-let expression (the +`block`) is evaluated only if the pattern match fails. Any bindings created +by the pattern match will be in scope after the if-not-let expression (but not +within its body). + +The body must diverge (i.e., it must panic, loop infinitely, call a diverging +function, or transfer control out of the enclosing block with a statement such +as `return`, `break`, or `continue`). Therefore, code immediately following +the if-not-let expression is evaluated only if the pattern match succeeds. + +An if-not-let expression has no `else` clause, because it is not needed. +(Instead of an `else` clause, code can simply be placed after the expression.) + +The type of an if-not-let expression is `()`. + +The following code: + +```rust +{ + if !let pattern = expression { + /* handle error */ + } + /* do something with `pattern` here */ +} +``` + +is equivalent to this code in current Rust: + +```rust +match expression { + pattern => { + /* do something with `pattern` here */ + } + _ => { + /* handle error */ + } +} +``` + + +# Drawbacks + +* Allowing a block expression to create bindings that live outside of its body + may be surprising. + +* `if !let` is not very visually distinct from `if let` due to the + similarities between the `!` and `l` glyphs. + +# Alternatives + +* Don't make any changes; use existing syntax like `if let` and `match` as + shown above, or write macros to simplify the code. + +* Consider alternate syntaxes for this feature, perhaps closer to Swift's `guard + let else`. + +# Unresolved questions + +[if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md +[swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525 From cf95a3baccd57fcba14f7c65924bffc8e65ed13d Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 1 Oct 2015 20:46:57 -0700 Subject: [PATCH 02/15] Fix typos in Servo example --- text/0000-if-not-let.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index f8592236df9..930683bc1ae 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -74,7 +74,7 @@ let subpage_layer_info = match layer_properties.subpage_layer_info { is equivalent to this much simpler if-not-let expression: ```rust -if !let Some(subpage_layer_info) = match layer_properties.subpage_layer_info { +if !let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info { return } ``` From 4d894c796ee315fa6ac41db5364c15ef27d6c462 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 1 Oct 2015 20:49:56 -0700 Subject: [PATCH 03/15] Add question about implementation --- text/0000-if-not-let.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index 930683bc1ae..e5c3db6aa3f 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -149,5 +149,7 @@ match expression { # Unresolved questions +* Is it feasible to implement the check that the body diverges? + [if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md [swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525 From cc19745cba67f807ece2ab8068468da23396d5ed Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 1 Oct 2015 21:15:45 -0700 Subject: [PATCH 04/15] Add a drawback about divergence requirement --- text/0000-if-not-let.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index e5c3db6aa3f..46572baa440 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -133,6 +133,9 @@ match expression { # Drawbacks +* “Must diverge” is an unusual requirement, which might be difficult to + explain or lead to confusing errors for programmers new to this feature. + * Allowing a block expression to create bindings that live outside of its body may be surprising. From f2a2b18d3246515310ddd610961616048aefc6b8 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 1 Oct 2015 21:43:05 -0700 Subject: [PATCH 05/15] Should it be a statement? --- text/0000-if-not-let.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index 46572baa440..fbd9d24a86c 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -154,5 +154,7 @@ match expression { * Is it feasible to implement the check that the body diverges? +* Should if-not-let be an expression, or a statement? + [if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md [swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525 From 1d4103b2dfe9a46b2deabfff83d9dd27a891c056 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 1 Oct 2015 21:50:13 -0700 Subject: [PATCH 06/15] Make a statement --- text/0000-if-not-let.md | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index fbd9d24a86c..5bca40daedb 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -1,4 +1,4 @@ -- Feature Name: if-not-let expression +- Feature Name: if-not-let statement - Start Date: 2015-09-30 - RFC PR: - Rust Issue: @@ -6,12 +6,11 @@ # Summary Introduce a new `if !let PAT = EXPR { BODY }` construct (informally called an -**if-not-let expression**). This works much like an if-let expression, but +**if-not-let statement**). This works much like an if-let expression, but executes its body when pattern matching fails. -This narrows the gap between regular `if` expressions and `if let` -expressions, while also providing a simpler syntax for some common -error-handling patterns. +This narrows the gap between regular `if` syntax and `if let` syntax, while +also simplifying some common error-handling patterns. # Motivation @@ -71,7 +70,7 @@ let subpage_layer_info = match layer_properties.subpage_layer_info { }; ``` -is equivalent to this much simpler if-not-let expression: +is equivalent to this much simpler if-not-let statement: ```rust if !let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info { @@ -80,31 +79,29 @@ if !let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info { ``` The Swift programming language, which inspired Rust's if-let expression, also -includes [guard-let-else][swift] expressions which are equivalent to this +includes a [guard-let-else][swift] statement which are equivalent to this proposal except for the choice of keywords. # Detailed design -Extend the Rust expression grammar to include the following production: +Extend the Rust statement grammar to include the following production: ``` -expr_if_not_let = 'if' '!' 'let' pat '=' expr block +stmt_if_not_let = 'if' '!' 'let' pat '=' expr block ``` -The pattern must be refutable. The body of the if-not-let expression (the +The pattern must be refutable. The body of the if-not-let statement (the `block`) is evaluated only if the pattern match fails. Any bindings created -by the pattern match will be in scope after the if-not-let expression (but not +by the pattern match will be in scope after the if-not-let statement (but not within its body). The body must diverge (i.e., it must panic, loop infinitely, call a diverging function, or transfer control out of the enclosing block with a statement such as `return`, `break`, or `continue`). Therefore, code immediately following -the if-not-let expression is evaluated only if the pattern match succeeds. +the if-not-let statement is evaluated only if the pattern match succeeds. -An if-not-let expression has no `else` clause, because it is not needed. -(Instead of an `else` clause, code can simply be placed after the expression.) - -The type of an if-not-let expression is `()`. +An if-not-let statement has no `else` clause, because it is not needed. +(Instead of an `else` clause, code can simply be placed after the body.) The following code: @@ -136,7 +133,7 @@ match expression { * “Must diverge” is an unusual requirement, which might be difficult to explain or lead to confusing errors for programmers new to this feature. -* Allowing a block expression to create bindings that live outside of its body +* Allowing an `if` statement to create bindings that live outside of its body may be surprising. * `if !let` is not very visually distinct from `if let` due to the @@ -154,7 +151,5 @@ match expression { * Is it feasible to implement the check that the body diverges? -* Should if-not-let be an expression, or a statement? - [if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md [swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525 From f18e5936af1158675b469a03a52bccb6ecbb44a0 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Thu, 1 Oct 2015 21:54:46 -0700 Subject: [PATCH 07/15] Sub-headings --- text/0000-if-not-let.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index 5bca40daedb..b3be0018676 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -22,6 +22,8 @@ within its body, which can force rightward drift and excessive nesting. `if !let` is a logical extension of `if let` that moves the failure case into the body, and allows the success case to follow without extra nesting. +## Example + For example, this code written with current Rust syntax: ```rust @@ -59,6 +61,8 @@ if !let Some(c) = z { */ ``` +## Versus `match` + It's possible to use `match` statements to emulate this today, but at a significant cost in length and readability. For example, this real-world code from Servo: From 43f1c4817798b9c8e3f2a95d98bf35a2673ef870 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 07:17:20 -0700 Subject: [PATCH 08/15] Add syntax as an alternative. --- text/0000-if-not-let.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index b3be0018676..3a31ec108bd 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -148,8 +148,9 @@ match expression { * Don't make any changes; use existing syntax like `if let` and `match` as shown above, or write macros to simplify the code. -* Consider alternate syntaxes for this feature, perhaps closer to Swift's `guard - let else`. +* Change the syntax to `let PAT = EXPR else { BODY }`, to make it more obvious + that this statement is introducing a new binding in the surrounding scope. + (Aside from the syntax, the statement works as described above.) # Unresolved questions From a2da984109ec81c19e4289b32a55c9069e10f5ae Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 07:17:44 -0700 Subject: [PATCH 09/15] Emphasize that it creates bindings in the enclosing scope --- text/0000-if-not-let.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index 3a31ec108bd..bc47ed7a30f 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -6,8 +6,11 @@ # Summary Introduce a new `if !let PAT = EXPR { BODY }` construct (informally called an -**if-not-let statement**). This works much like an if-let expression, but -executes its body when pattern matching fails. +**if-not-let statement**). + +If the pattern match succeeds, its binding are introduced *into the +surrounding scope*. If it does not succeed, it must diverge (e.g., return or +break). You can think of if-not-let as a “refutable `let` statement.” This narrows the gap between regular `if` syntax and `if let` syntax, while also simplifying some common error-handling patterns. From 1dd2acff1c7e85002d224a76aab3ba71c04c260f Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 07:19:12 -0700 Subject: [PATCH 10/15] Formatting, typos --- text/0000-if-not-let.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index bc47ed7a30f..c2f797bbc77 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -8,7 +8,7 @@ Introduce a new `if !let PAT = EXPR { BODY }` construct (informally called an **if-not-let statement**). -If the pattern match succeeds, its binding are introduced *into the +If the pattern match succeeds, its bindings are introduced *into the surrounding scope*. If it does not succeed, it must diverge (e.g., return or break). You can think of if-not-let as a “refutable `let` statement.” @@ -18,7 +18,7 @@ also simplifying some common error-handling patterns. # Motivation [if-let expressions][if-let] offer a succinct syntax for pattern matching -with only one "success" path. This is particularly useful for unwrapping +with only one “success” path. This is particularly useful for unwrapping types like `Option`. However, an if-let expression can only create bindings within its body, which can force rightward drift and excessive nesting. @@ -33,9 +33,9 @@ For example, this code written with current Rust syntax: if let Some(a) = x { if let Some(b) = y { if let Some(c) = z { - /* - * do something with a, b, and c - */ + // ... + do_something_with(a, b, c); + // ... } else { return Err("bad z"); } @@ -59,9 +59,9 @@ if !let Some(b) = y { if !let Some(c) = z { return Err("bad z"); } -/* - * do something with a, b, and c - */ +// ... +do_something_with(a, b, c); +// ... ``` ## Versus `match` @@ -86,7 +86,7 @@ if !let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info { ``` The Swift programming language, which inspired Rust's if-let expression, also -includes a [guard-let-else][swift] statement which are equivalent to this +includes a [guard-let-else][swift] statement which is equivalent to this proposal except for the choice of keywords. # Detailed design From 09db5e2ba565039f606c00f20a11daab68750fd0 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 08:07:25 -0700 Subject: [PATCH 11/15] Simplify match expansion --- text/0000-if-not-let.md | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index c2f797bbc77..aec761c82b5 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -113,25 +113,19 @@ An if-not-let statement has no `else` clause, because it is not needed. The following code: ```rust -{ - if !let pattern = expression { - /* handle error */ - } - /* do something with `pattern` here */ +if !let pattern = expression { + body } ``` is equivalent to this code in current Rust: ```rust -match expression { - pattern => { - /* do something with `pattern` here */ - } - _ => { - /* handle error */ - } -} +// `(a, b, c, ...)` is the list of all bindings in `pattern`. +let (a, b, c, ...) = match expression { + pattern => (a, b, c, ...), + _ => { body } +}; ``` From 661eb777f22a95faf38894550ecfa8ed37977398 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 09:18:13 -0700 Subject: [PATCH 12/15] Change to let...else syntax --- text/0000-if-not-let.md | 49 ++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 30 deletions(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index aec761c82b5..057d37994e8 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -1,16 +1,16 @@ -- Feature Name: if-not-let statement +- Feature Name: let-else statement - Start Date: 2015-09-30 - RFC PR: - Rust Issue: # Summary -Introduce a new `if !let PAT = EXPR { BODY }` construct (informally called an -**if-not-let statement**). +Introduce a new `let PAT = EXPR else { BODY }` construct (informally called an +**let-else statement**). If the pattern match succeeds, its bindings are introduced *into the surrounding scope*. If it does not succeed, it must diverge (e.g., return or -break). You can think of if-not-let as a “refutable `let` statement.” +break). You can think of let-else as a “refutable `let` statement.” This narrows the gap between regular `if` syntax and `if let` syntax, while also simplifying some common error-handling patterns. @@ -22,9 +22,6 @@ with only one “success” path. This is particularly useful for unwrapping types like `Option`. However, an if-let expression can only create bindings within its body, which can force rightward drift and excessive nesting. -`if !let` is a logical extension of `if let` that moves the failure case into -the body, and allows the success case to follow without extra nesting. - ## Example For example, this code written with current Rust syntax: @@ -50,13 +47,13 @@ if let Some(a) = x { would become: ```rust -if !let Some(a) = x { +let Some(a) = x else { return Err("bad x"); } -if !let Some(b) = y { +let Some(b) = y else { return Err("bad y"); } -if !let Some(c) = z { +let Some(c) = z else { return Err("bad z"); } // ... @@ -77,10 +74,10 @@ let subpage_layer_info = match layer_properties.subpage_layer_info { }; ``` -is equivalent to this much simpler if-not-let statement: +is equivalent to this much simpler let-else statement: ```rust -if !let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info { +let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info else { return } ``` @@ -94,26 +91,23 @@ proposal except for the choice of keywords. Extend the Rust statement grammar to include the following production: ``` -stmt_if_not_let = 'if' '!' 'let' pat '=' expr block +stmt_let_else = 'let' pat '=' expr 'else' block ``` -The pattern must be refutable. The body of the if-not-let statement (the +The pattern must be refutable. The body of the let-else statement (the `block`) is evaluated only if the pattern match fails. Any bindings created -by the pattern match will be in scope after the if-not-let statement (but not +by the pattern match will be in scope after the let-else statement (but not within its body). The body must diverge (i.e., it must panic, loop infinitely, call a diverging function, or transfer control out of the enclosing block with a statement such as `return`, `break`, or `continue`). Therefore, code immediately following -the if-not-let statement is evaluated only if the pattern match succeeds. - -An if-not-let statement has no `else` clause, because it is not needed. -(Instead of an `else` clause, code can simply be placed after the body.) +the let-else statement is evaluated only if the pattern match succeeds. The following code: ```rust -if !let pattern = expression { +let pattern = expression else { body } ``` @@ -128,30 +122,25 @@ let (a, b, c, ...) = match expression { }; ``` - # Drawbacks * “Must diverge” is an unusual requirement, which might be difficult to explain or lead to confusing errors for programmers new to this feature. -* Allowing an `if` statement to create bindings that live outside of its body - may be surprising. - -* `if !let` is not very visually distinct from `if let` due to the - similarities between the `!` and `l` glyphs. +* To a human scanning the code, it's not obvious when looking at the start of + a statement whether it is a `let ... else` or a regular `let` statement. # Alternatives * Don't make any changes; use existing syntax like `if let` and `match` as shown above, or write macros to simplify the code. -* Change the syntax to `let PAT = EXPR else { BODY }`, to make it more obvious - that this statement is introducing a new binding in the surrounding scope. - (Aside from the syntax, the statement works as described above.) +* Use the same semantics with different syntax. For example, the original + version of this RFC used `if !let PAT = EXPR { BODY }`. # Unresolved questions -* Is it feasible to implement the check that the body diverges? +* How much implementation complexity does this add? [if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md [swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525 From d8143d9019b6b394fc0bcbcf70adc270324775a1 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 09:22:42 -0700 Subject: [PATCH 13/15] Remove note about syntax --- text/0000-if-not-let.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index 057d37994e8..9d48f22b928 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -12,8 +12,8 @@ If the pattern match succeeds, its bindings are introduced *into the surrounding scope*. If it does not succeed, it must diverge (e.g., return or break). You can think of let-else as a “refutable `let` statement.” -This narrows the gap between regular `if` syntax and `if let` syntax, while -also simplifying some common error-handling patterns. +This simplifies some common error-handling patterns, and reduces the need for +special-purpose control flow macros. # Motivation From d234be6fd2a5e4c3456ad28e2fffe8dca671c43c Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 09:24:20 -0700 Subject: [PATCH 14/15] Expand comparison to --- text/0000-if-not-let.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index 9d48f22b928..d273f75c606 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -22,6 +22,9 @@ with only one “success” path. This is particularly useful for unwrapping types like `Option`. However, an if-let expression can only create bindings within its body, which can force rightward drift and excessive nesting. +let-else statements move the “failure” case into the body, while allowing +the “success” case to continue without additional nesting. + ## Example For example, this code written with current Rust syntax: From aadaad5575e93e2a79674a90fa4334ece1266bc0 Mon Sep 17 00:00:00 2001 From: Matt Brubeck Date: Fri, 2 Oct 2015 10:39:16 -0700 Subject: [PATCH 15/15] Rename file --- text/0000-if-not-let.md | 150 +--------------------------------------- text/0000-let-else.md | 149 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+), 149 deletions(-) create mode 100644 text/0000-let-else.md diff --git a/text/0000-if-not-let.md b/text/0000-if-not-let.md index d273f75c606..3bc4455ec4f 100644 --- a/text/0000-if-not-let.md +++ b/text/0000-if-not-let.md @@ -1,149 +1 @@ -- Feature Name: let-else statement -- Start Date: 2015-09-30 -- RFC PR: -- Rust Issue: - -# Summary - -Introduce a new `let PAT = EXPR else { BODY }` construct (informally called an -**let-else statement**). - -If the pattern match succeeds, its bindings are introduced *into the -surrounding scope*. If it does not succeed, it must diverge (e.g., return or -break). You can think of let-else as a “refutable `let` statement.” - -This simplifies some common error-handling patterns, and reduces the need for -special-purpose control flow macros. - -# Motivation - -[if-let expressions][if-let] offer a succinct syntax for pattern matching -with only one “success” path. This is particularly useful for unwrapping -types like `Option`. However, an if-let expression can only create bindings -within its body, which can force rightward drift and excessive nesting. - -let-else statements move the “failure” case into the body, while allowing -the “success” case to continue without additional nesting. - -## Example - -For example, this code written with current Rust syntax: - -```rust -if let Some(a) = x { - if let Some(b) = y { - if let Some(c) = z { - // ... - do_something_with(a, b, c); - // ... - } else { - return Err("bad z"); - } - } else { - return Err("bad y"); - } -} else { - return Err("bad x"); -} -``` - -would become: - -```rust -let Some(a) = x else { - return Err("bad x"); -} -let Some(b) = y else { - return Err("bad y"); -} -let Some(c) = z else { - return Err("bad z"); -} -// ... -do_something_with(a, b, c); -// ... -``` - -## Versus `match` - -It's possible to use `match` statements to emulate this today, but at a -significant cost in length and readability. For example, this real-world code -from Servo: - -```rust -let subpage_layer_info = match layer_properties.subpage_layer_info { - Some(ref subpage_layer_info) => *subpage_layer_info, - None => return, -}; -``` - -is equivalent to this much simpler let-else statement: - -```rust -let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info else { - return -} -``` - -The Swift programming language, which inspired Rust's if-let expression, also -includes a [guard-let-else][swift] statement which is equivalent to this -proposal except for the choice of keywords. - -# Detailed design - -Extend the Rust statement grammar to include the following production: - -``` -stmt_let_else = 'let' pat '=' expr 'else' block -``` - -The pattern must be refutable. The body of the let-else statement (the -`block`) is evaluated only if the pattern match fails. Any bindings created -by the pattern match will be in scope after the let-else statement (but not -within its body). - -The body must diverge (i.e., it must panic, loop infinitely, call a diverging -function, or transfer control out of the enclosing block with a statement such -as `return`, `break`, or `continue`). Therefore, code immediately following -the let-else statement is evaluated only if the pattern match succeeds. - -The following code: - -```rust -let pattern = expression else { - body -} -``` - -is equivalent to this code in current Rust: - -```rust -// `(a, b, c, ...)` is the list of all bindings in `pattern`. -let (a, b, c, ...) = match expression { - pattern => (a, b, c, ...), - _ => { body } -}; -``` - -# Drawbacks - -* “Must diverge” is an unusual requirement, which might be difficult to - explain or lead to confusing errors for programmers new to this feature. - -* To a human scanning the code, it's not obvious when looking at the start of - a statement whether it is a `let ... else` or a regular `let` statement. - -# Alternatives - -* Don't make any changes; use existing syntax like `if let` and `match` as - shown above, or write macros to simplify the code. - -* Use the same semantics with different syntax. For example, the original - version of this RFC used `if !let PAT = EXPR { BODY }`. - -# Unresolved questions - -* How much implementation complexity does this add? - -[if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md -[swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525 +Moved to [0000-let-else.md](0000-let-else.md). diff --git a/text/0000-let-else.md b/text/0000-let-else.md new file mode 100644 index 00000000000..d273f75c606 --- /dev/null +++ b/text/0000-let-else.md @@ -0,0 +1,149 @@ +- Feature Name: let-else statement +- Start Date: 2015-09-30 +- RFC PR: +- Rust Issue: + +# Summary + +Introduce a new `let PAT = EXPR else { BODY }` construct (informally called an +**let-else statement**). + +If the pattern match succeeds, its bindings are introduced *into the +surrounding scope*. If it does not succeed, it must diverge (e.g., return or +break). You can think of let-else as a “refutable `let` statement.” + +This simplifies some common error-handling patterns, and reduces the need for +special-purpose control flow macros. + +# Motivation + +[if-let expressions][if-let] offer a succinct syntax for pattern matching +with only one “success” path. This is particularly useful for unwrapping +types like `Option`. However, an if-let expression can only create bindings +within its body, which can force rightward drift and excessive nesting. + +let-else statements move the “failure” case into the body, while allowing +the “success” case to continue without additional nesting. + +## Example + +For example, this code written with current Rust syntax: + +```rust +if let Some(a) = x { + if let Some(b) = y { + if let Some(c) = z { + // ... + do_something_with(a, b, c); + // ... + } else { + return Err("bad z"); + } + } else { + return Err("bad y"); + } +} else { + return Err("bad x"); +} +``` + +would become: + +```rust +let Some(a) = x else { + return Err("bad x"); +} +let Some(b) = y else { + return Err("bad y"); +} +let Some(c) = z else { + return Err("bad z"); +} +// ... +do_something_with(a, b, c); +// ... +``` + +## Versus `match` + +It's possible to use `match` statements to emulate this today, but at a +significant cost in length and readability. For example, this real-world code +from Servo: + +```rust +let subpage_layer_info = match layer_properties.subpage_layer_info { + Some(ref subpage_layer_info) => *subpage_layer_info, + None => return, +}; +``` + +is equivalent to this much simpler let-else statement: + +```rust +let Some(ref subpage_layer_info) = layer_properties.subpage_layer_info else { + return +} +``` + +The Swift programming language, which inspired Rust's if-let expression, also +includes a [guard-let-else][swift] statement which is equivalent to this +proposal except for the choice of keywords. + +# Detailed design + +Extend the Rust statement grammar to include the following production: + +``` +stmt_let_else = 'let' pat '=' expr 'else' block +``` + +The pattern must be refutable. The body of the let-else statement (the +`block`) is evaluated only if the pattern match fails. Any bindings created +by the pattern match will be in scope after the let-else statement (but not +within its body). + +The body must diverge (i.e., it must panic, loop infinitely, call a diverging +function, or transfer control out of the enclosing block with a statement such +as `return`, `break`, or `continue`). Therefore, code immediately following +the let-else statement is evaluated only if the pattern match succeeds. + +The following code: + +```rust +let pattern = expression else { + body +} +``` + +is equivalent to this code in current Rust: + +```rust +// `(a, b, c, ...)` is the list of all bindings in `pattern`. +let (a, b, c, ...) = match expression { + pattern => (a, b, c, ...), + _ => { body } +}; +``` + +# Drawbacks + +* “Must diverge” is an unusual requirement, which might be difficult to + explain or lead to confusing errors for programmers new to this feature. + +* To a human scanning the code, it's not obvious when looking at the start of + a statement whether it is a `let ... else` or a regular `let` statement. + +# Alternatives + +* Don't make any changes; use existing syntax like `if let` and `match` as + shown above, or write macros to simplify the code. + +* Use the same semantics with different syntax. For example, the original + version of this RFC used `if !let PAT = EXPR { BODY }`. + +# Unresolved questions + +* How much implementation complexity does this add? + +[if-let]: https://github.com/rust-lang/rfcs/blob/master/text/0160-if-let.md +[swift]: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html#//apple_ref/doc/uid/TP40014097-CH9-ID525