- Feature Name: let-else statement
- Start Date: 2015-09-30
- RFC PR:
- Rust Issue:
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.
if-let expressions 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.
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, c);
// ...
} else {
return Err("bad z");
}
} else {
return Err("bad y");
}
} else {
return Err("bad x");
}
would become:
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);
// ...
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:
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:
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 statement which is equivalent to this proposal except for the choice of keywords.
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:
let pattern = expression else {
body
}
is equivalent to this code in current Rust:
// `(a, b, c, ...)` is the list of all bindings in `pattern`.
let (a, b, c, ...) = match expression {
pattern => (a, b, c, ...),
_ => { body }
};
-
“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 regularlet
statement.
-
Don't make any changes; use existing syntax like
if let
andmatch
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 }
.
- How much implementation complexity does this add?