Summary

Enables "or" patterns for if let and while let expressions

as well as let and for statements.
In other words, examples like the following are now possible:

#![allow(unused)] fn main() { enum E<T> { A(T), B(T), C, D, E, F } // Assume the enum E and the following for the remainder of the RFC: use E::*; let x = A(1); let r = if let C | D = x { 1 } else { 2 }; while let A(x) | B(x) = source() { react_to(x); } enum ParameterKind<T, L = T> { Ty(T), Lifetime(L), } use ParameterKind::*; // Only possible when `L = T` such that `kind : ParameterKind<T, T>`. let Ty(x) | Lifetime(x) = kind; for Ty(x) | Lifetime(x) in ::std::iter::once(kind); }

Motivation

While nothing in this RFC is currently impossible in Rust, the changes the RFC proposes improves the ergonomics of control

制御する
flow when dealing with enums (sum types) with three or more variants where the program should react in one way to a group of variants, and another way to another group of variants. Examples of when such sum types occur
起こる
are protocols, when dealing with languages
言語
(ASTs), and non-trivial iterators.

The following snippet

断片
(written with this RFC):

#![allow(unused)] fn main() { if let A(x) | B(x) = expr { do_stuff_with(x); } }

must be written as:

#![allow(unused)] fn main() { if let A(x) = expr { do_stuff_with(x); } else if let B(x) = expr { do_stuff_with(x); } }

or, using match:

#![allow(unused)] fn main() { match expr { A(x) | B(x) => do_stuff_with(x), _ => {}, } }

This way of using match is seen multiple

複数の
times in std::iter when dealing with the Chain iterator adapter. An example of this is:

#![allow(unused)] fn main() { fn fold<Acc, F>(self, init: Acc, mut f: F) -> Acc where F: FnMut(Acc, Self::Item) -> Acc, { let mut accum = init; match self.state { ChainState::Both | ChainState::Front => { accum = self.a.fold(accum, &mut f); } _ => { } } match self.state { ChainState::Both | ChainState::Back => { accum = self.b.fold(accum, &mut f); } _ => { } } accum } }

which could have been written as:

#![allow(unused)] fn main() { fn fold<Acc, F>(self, init: Acc, mut f: F) -> Acc where F: FnMut(Acc, Self::Item) -> Acc, { use ChainState::*; let mut accum = init; if let Both | Front = self.state { accum = self.a.fold(accum, &mut f); } if let Both | Back = self.state { accum = self.b.fold(accum, &mut f); } accum } }

This version is both shorter and clearer.

With while let, the ergonomics and in particular the readability

可読性
can be significantly
著しく
improved.

The following snippet

断片
(written with this RFC):

#![allow(unused)] fn main() { while let A(x) | B(x) = source() { react_to(x); } }

must currently be written as:

#![allow(unused)] fn main() { loop { match source() { A(x) | B(x) => react_to(x), _ => { break; } } } }

Another major motivation of the RFC is consistency with match.

To keep let and for statements

consistent with if let, and to enable the scenario exemplified by ParameterKind in the motivation, these or-patterns are allowed
許可する、可能にする
at the top level of let and for statements.

In addition

追加
to the ParameterKind example, we can also consider
考える、みなす
slice.binary_search(&x). If we are only interested in the index at where x is or would be, without any regard for if it was there or not, we can now simply write:

#![allow(unused)] fn main() { let Ok(index) | Err(index) = slice.binary_search(&x); }

and we will get back the index in any case and continue on from there.

Guide-level explanation

RFC 2005, in describing

記述する
the third example in the section
"Examples", refers
参照する
to patterns with | in them as "or" patterns. This RFC adopts the same terminology.

While the "sum" of all patterns in match must be irrefutable, or in other words: cover all cases, be exhaustive, this is not the case (currently) with if/while let, which may have a refutable pattern. This RFC does not change this.

The RFC only extends

拡張する
the use of or-patterns at the top level from matches to if let and while let expressions
as well as let and for statements.

For examples, see motivation.

Reference-level explanation

Grammar
文法

if let

The grammar

文法
in § 7.2.24 is changed from:

if_let_expr : "if" "let" pat '=' expr '{' block '}' else_tail ? ;

to:

if_let_expr : "if" "let" '|'? pat [ '|' pat ] * '=' expr '{' block '}' else_tail ? ;

while let

The grammar

文法
in § 7.2.25 is changed from:

while_let_expr : [ lifetime ':' ] ? "while" "let" pat '=' expr '{' block '}' ;

to:

while_let_expr : [ lifetime ':' ] ? "while" "let" '|'? pat [ '|' pat ] * '=' expr '{' block '}' ;

for

The expr_for grammar

文法
is changed from:

expr_for : maybe_label FOR pat IN expr_nostruct block ;

to:

expr_for : maybe_label FOR '|'? pat ('|' pat)* IN expr_nostruct block ;

let statements

The statement

stmt grammar
文法
is replaced with a language
言語
equivalent
等価
to:

stmt ::= old_stmt_grammar | let_stmt_many ; let_stmt_many ::= "let" pat_two_plus "=" expr ";" pat_two_plus ::= '|'? pat [ '|' pat ] + ;

Syntax
文法
lowering
下方の、小文字の

The changes proposed in this RFC with respect to if let, while let, and for can be implemented

実装する
by transforming the if/while let constructs
作る、構成体
with a syntax-lowering pass into match and loop + match expressions.

Meanwhile, let statements

can be transformed into a continuation with match as described
記述する
below.

Examples, if let

These examples are extensions on the if let RFC. Therefore, the RFC avoids

避ける、回避する
duplicating any details already specified
特定する、指定する、規定する
there.

Source:

元の

#![allow(unused)] fn main() { if let |? PAT [| PAT]* = EXPR { BODY } }

Result:

結果、戻り値

#![allow(unused)] fn main() { match EXPR { PAT [| PAT]* => { BODY } _ => {} } }

Source:

元の

#![allow(unused)] fn main() { if let |? PAT [| PAT]* = EXPR { BODY_IF } else { BODY_ELSE } }

Result:

結果、戻り値

#![allow(unused)] fn main() { match EXPR { PAT [| PAT]* => { BODY_IF } _ => { BODY_ELSE } } }

Source:

元の

#![allow(unused)] fn main() { if COND { BODY_IF } else if let |? PAT [| PAT]* = EXPR { BODY_ELSE_IF } else { BODY_ELSE } }

Result:

結果、戻り値

#![allow(unused)] fn main() { if COND { BODY_IF } else { match EXPR { |? PAT [| PAT]* => { BODY_ELSE_IF } _ => { BODY_ELSE } } } }

Source

元の

#![allow(unused)] fn main() { if let |? PAT [| PAT]* = EXPR { BODY_IF } else if COND { BODY_ELSE_IF_1 } else if OTHER_COND { BODY_ELSE_IF_2 } }

Result:

結果、戻り値

#![allow(unused)] fn main() { match EXPR { |? PAT [| PAT]* => { BODY_IF } _ if COND => { BODY_ELSE_IF_1 } _ if OTHER_COND => { BODY_ELSE_IF_2 } _ => {} } }

Examples, while let

The following example is an extension on the while let RFC.

Source

元の

#![allow(unused)] fn main() { ['label:] while let |? PAT [| PAT]* = EXPR { BODY } }

Result:

結果、戻り値

#![allow(unused)] fn main() { ['label:] loop { match EXPR { PAT [| PAT]* => BODY, _ => break } } }

Examples, for

Assuming that the semantics of for is defined

定義する
by a desugaring from:

#![allow(unused)] fn main() { for PAT in EXPR_ITER { BODY } }

into:

#![allow(unused)] fn main() { match IntoIterator::into_iter(EXPR_ITER) { mut iter => loop { let next = match iter.next() { Some(val) => val, None => break, }; let PAT = next; { BODY }; }, }; }

then the only thing that changes is that PAT may include | at the top level in the for loop and the desugaring as per the section

on grammar.
文法

Desugaring let statements
with | in the top-level pattern

There continues to be an exhaustivity check in let statements,

however this check will now be able to support multiple
複数の
patterns.

This is a possible desugaring that a Rust compiler may do. While such a compiler may elect to implement

実装する
this differently, these semantics should be kept.

Source:

元の

#![allow(unused)] fn main() { { // prefix of statements: stmt* // The let statement which is the cause for desugaring: let_stmt_many // the continuation / suffix of statements: stmt* tail_expr? // Meta-variable for optional tail expression without ; at end } }

Result

結果、戻り値

#![allow(unused)] fn main() { { stmt* match expr { pat_two_plus => { stmt* tail_expr? } } } }

For example, the following code:

#![allow(unused)] fn main() { { foo(); bar(); let Ok(index) | Err(index) = slice.binary_search(&thing); println!("{}", index); do_something_to(index) } }

can be desugared to

#![allow(unused)] fn main() { { foo(); bar(); match slice.binary_search(&thing) { Ok(index) | Err(index) => { println!("{}", index); do_something_to(index) } } } }

It can also be desugared to:

#![allow(unused)] fn main() { { foo(); bar(); let index = match slice.binary_search(&thing) { Ok(index) | Err(index) => index, } println!("{}", index); do_something_to(index) } }

(Both are equivalent)

Drawbacks

This adds more additions

追加
to the grammar
文法
and makes the compiler more complex.
複素数、複文の

Rationale and alternatives
代わりのもの、選択肢

This could simply not be done. Consistency with match is however on its own reason enough to do this.

It could be claimed that the if/while let RFCs already mandate this RFC, this RFC does answer that question and instead simply mandates it now.

Another alternative

代わりのもの、選択肢
is to only deal with if/while let expressions
but not let and for statements.

Unresolved questions

The exact syntax

文法
transformations should be deferred to the implementation.
実装
This RFC does not mandate exactly
正確に
how the AST:s should be transformed, only that the or-pattern feature be supported.

There are no unresolved questions.