- Feature Name:
match_colon_syntax - Start Date: 2019-06-22
- RFC PR: rust-lang/rfcs#0000
- Rust Issue: rust-lang/rust#0000
Summary
This RFC proposes adding alternative syntax for non-exhaustive branching based
on pattern matching: the match : and match while syntax.
If the syntax is added,
match <expr> : <pattern> => {
// statements
}
and
match <expr1> : <pattern> => <expr2>
would respectively be equivalent to current
if let <pattern> = <expr> {
// statements
}
and
if let <pattern> = <expr1> {
<expr2>
}
. The match while syntax will have a similar equivalence to the current
while let syntax.
Motivation
The current if let and while let syntax makes it easy for programmers to
confuse let statements as expressions (with a following ;) that return
bool values. Let me call a let statement with its ; stripped a "let
clause".
Since Rust is an expression-oriented language, when one is not sure about a
langauge element, they will assume that it is an expression. The let clause in
Rust is not an expression. This creates an exception and causes some
inconsistency and inconvenience, but the problem is not too huge. Just as this
RFC
mentioned, one will very quickly learn that this syntax does not create an
expression by some simple experiments. However, the if let and while let
syntax makes the problem worse. In if let and while let syntax, the usage of
the let clause is exactly the same as if the let clause creates an
expression that returns a bool value. In other words, currently, a learner who
is not sure about whether the let clause creates an expression will be given
both clues implying that it does and clues implying that it does not.
Programmers will sometimes get confused and try to use let clauses as bool
expressions.
In this
RFC,
it is given as a support material that programmers try to chain let clauses
with &&. This is a sign of confusing let clauses as expressions. It is also
mentioned in this RFC that using || with let clauses has been attempted and
allowing it will be considered in the future. In this
RFC, it is proposed that using
! to negate let clauses should be supported. I think these are also proof
that programmers are confusing let clauses as expressions.
The RFC proposing && chain with let clauses is accepted. Once it is
implemented and and the new version is published, the let syntax would be even
more similar to a bool expression. The !let and "let with ||" proposals
will create even more cases where let clauses are used as an expression. This
trend will possibly cause a result that beginners will need to memorize special
cases. They will need a list or a cheatsheet to help them to know whether a
let clause can be used as a bool expression in a certain case.
I feel this trend is dangerous, so I want to propose new syntax which does
not use let to possibly stop this trend, or at least to give people who do
not want deal with the confusion an alternative.
Guide-level explanation
match <expr> : <pattern> => {
// statements
}
and
match <expr> : <pattern> => <expr2>
would respectively be equivalent to current
if let <pattern> = <expr> {
// statements
}
and
if let <pattern> = <expr> {
<expr2>
}
. The match while syntax will have a similar equivalence to the current
while let syntax.
Of course, just as the original match syntax, you can use | to match
multiple patterns:
match <expr> : <pattern1> | <pattern2> => {
// statements
}
If you want to chain multiple pattern matching conditions, you can write
match <expr1> : <pattern1> =>
match <expr2> : <pattern2> | <pattern3> =>
match <expr3> : <pattern4> => {
// statements
}
To integrate boolean conditions and adding boolean guards into it:
match <expr1> : <pattern1> =>
match <expr2> : <pattern2> | <pattern3> =>
match <expr3> : <pattern4> if flag0 == true | <pattern5> =>
if a == 4 || b <= 3 && flag1 == true {
// statements
}
This syntax does not have else, but when you try to write
if let <pattern> = <expr> {
// some statements
} else {
// more statements
}
, you can always write
match <expr> {
<pattern> => {
// some statements
},
_ => {
// more statements
}
}
Reference-level explanation
We replace the following production:
expr : literal | path | tuple_expr | unit_expr | struct_expr
| block_expr | method_call_expr | field_expr | array_expr
| idx_expr | range_expr | unop_expr | binop_expr
| paren_expr | call_expr | lambda_expr | while_expr
| loop_expr | break_expr | continue_expr | for_expr
| if_expr | match_expr | if_let_expr | while_let_expr
| return_expr ;
with:
expr : literal | path | tuple_expr | unit_expr | struct_expr
| block_expr | method_call_expr | field_expr | array_expr
| idx_expr | range_expr | unop_expr | binop_expr
| paren_expr | call_expr | lambda_expr | while_expr
| loop_expr | break_expr | continue_expr | for_expr
| if_expr | match_expr | match_colon_expr | match_while_expr
| if_let_expr | while_let_expr | return_expr ;
match_colon_expr : "match" no_struct_literal_expr ":" match_colon_arm ;
match_colon_arm : attribute * match_pat "=>" [ expr | '{' block '}' ] ;
match_pat : pat [ '|' pat ] * [ "if" expr ] : ;
Note that match_pat is the same as the original one used with match_expr.
Drawbacks
Redundant: this syntax can be considered as a syntax sugar for if let, which
is already a syntax sugar for match with only one meaningful branch…
Limited: This syntax does not have equivalence to if let PATTERN = EXPR { } else { }.
This RFC creates a possible pitfall:
This
RFC
introduces while let && syntax:
while let <pattern1> = <expr1>
&& let <pattern2> = <expr2>
&& let <pattern3> = <expr3> {
// statements
}
. The code above is NOT equivalent to
match <expr1> while <pattern1> =>
match <expr2> while <pattern2> =>
match <expr3> while <pattern3> => {
// statements
}
The former is one single loop with multiple conditions, while the later one is
multiple nested loops, each having one condition. An equivalence using the
match while syntax would be
loop {
match <expr1> : <pattern1> =>
match <expr2> : <pattern2> =>
match <expr3> : <pattern3> => {
// statements
continue;
}
break;
}
which is sort of awkward, but I wonder how popular this use case is.
The problem of duplicate code in else clauses cannot be solved by this syntax.
Also, generally the match syntax is a little more verbose than the if let
syntax.
Another issue is, : does not align well with while.
Rationale and alternatives
Why is this design the best in the space of possible designs:
This design uses one single language element (the match : syntax) to support
- Simple non-exhaustive pattern matching conditional execution.
- Non-exhaustive pattern matching branching with multiple conditions that should all be satisfied (the “and” case).
2 is proposed and accepted here.
Also, it is totally consistent with the current match syntax. More importantly,
unlike if let and while let, it does (should) not cause any confusions.
This introduction of this syntax will also enable a strong binding between
keywords and their jobs. If a programmer completely uses match on to replace
if let, then when reading their code, whenever one sees match, they know
they have a branching here, and when one sees let, they know here is a
(simple) declaration.
We can also possibly extend this syntax to support matching negation which is proposed here. The method is in the future possibilities section.
What other designs have been considered and what is the rationale for not choosing them:
The usage of : can certainly be discussed more. I am using it here because
if cannot be used as it is already used for guards, and using : does not
need any additional keywords. If people do not like it, it can certainly be
changed into other things, like @, on, only, nex (for non-exhaustive),
etc.
This RFC’s rationale section provides very good insights on other possible designs. I agree with all of them except for
- I do think causing programmers to confuse
letclauses as expressions is a big problem. - I do think current the
matchsyntax places patterns at lhs and expressions at rhs, so it is acceptable to have a syntax which starts withmatchthat has such a lhs/rhs configuration.
What is the impact of not doing this:
Just as mentioned in the Motivation section, we are having a dangerous trend
of making let clauses bool expressions in some cases but no other cases.
This will create pitfalls and difficulties in memorization for future Rust
learners. For a language that has the potential to replace C++, I really hope
this will not happen. Also, I do not think it a good idea to just make the
clauses bool expressions.
Prior art
Swift
Swift uses let for all pattern matching. I feel it is where Rust developer got
the inspiration of if let and while let. However, what is good for Swift
might be bad for Rust. Swift is not an expression-oriented language. One can
only blame themselves if they assumed anything in Swift is an expression, so
have such syntax will not cause as strong a confusion as what the syntax can
cause in Rust. Also, Swift uses let anywhere there is a pattern matching. This
is not the case in Rust. We also have match in Rust which is specialized in
branching (for comparison, let statements in Rust are specialized for
declaration). I think it makes more sense to make all syntax used for branching
similar to each other.
Unresolved questions
What parts of the design do you expect to resolve through the RFC process before this gets merged:
The symbol/keyword separating the pattern and expression can be further discussed.
What parts of the design do you expect to resolve through the implementation of this feature before stabilization:
What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC:
Should we make let <pattern> = <expr> clauses expressions: I think for
consistency we should, but it should not return a bool. That would be
confusing. I think it can either return <expr> like C/C++ or simply return
().
Future possibilities
Negation
We can extend this syntax to support matching negation proposed here. The syntax would be like this:
match <expr> : not <pattern> => {
}
Deprecation
Personally I hope if let and while let would get deprecated. I do not think
that can happen, though.