Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC for `if let` expression #160
Conversation
lilyball
referenced this pull request
Jul 9, 2014
Closed
Add a matches!(expr, pattern) macro. #14685
This comment has been minimized.
This comment has been minimized.
|
I like this! |
This comment has been minimized.
This comment has been minimized.
|
This has interesting potential. Where I might currently write match foo {
A { .. } => {
a
}
B(..) => {
b
}
C => {
c
}
_ => {
d
}
}I could now write if let A { .. } = foo {
a
} else if let B(..) = foo {
b
} else if let C = foo {
c
} else {
d
}Exhaustiveness checking wouldn’t come with this way of doing it, though. (Leastways, not easily, and making it an error would be distinctly suspect.) I think you’re probably right about guards. Although it could potentially be nice to have them, |
This comment has been minimized.
This comment has been minimized.
sinistersnare
commented
Jul 9, 2014
|
I don't know if I like this, it adds unneeded complexity to the language, and is just sugar for a time in this language where we do not need/want sugar (of course this could be a post 1.0 thing then disregard that part). Also it is not the easiest thing to grok, it confused me for a good bit. I also dislike the syntax, but if it gets accepted I do not think I will complain very much. -1 |
This comment has been minimized.
This comment has been minimized.
nielsle
commented
Jul 9, 2014
|
Just a nitpick: If optVal is an option, then you can also do optVal.map( |x|
do_something_with(*x)
); |
This comment has been minimized.
This comment has been minimized.
|
@nielsle Only if using a closure isn't a problem, which it often can be, and if you have no need for an |
This comment has been minimized.
This comment has been minimized.
|
@sinistersnare What do you mean, a time in this language where we do not need/want sugar? |
This comment has been minimized.
This comment has been minimized.
sinistersnare
commented
Jul 9, 2014
|
The way I see it, we are striping the language to its bare essentials and necessary features for 1.0, and working out ergonomics from there. As you said, this could be easily desugared into a match block, whereas a for loop is a classic idiom from many programming languages. I consider the way for loops are done in Rust a huge selling point for the language, safer, faster, and better looking. I just do not think this would be greatly beneficial to Rust, but if the majority opinion says go, sure why not. |
This comment has been minimized.
This comment has been minimized.
bachm
commented
Jul 9, 2014
|
To me it seems the benefit of this is essentially not having to write the
|
This comment has been minimized.
This comment has been minimized.
netvl
commented
Jul 9, 2014
|
@bachm, I think the main benefit is that there is no extra nesting and indentation here. Your The proposal looks great, +1 from me. |
This comment has been minimized.
This comment has been minimized.
stepancheg
commented
Jul 9, 2014
|
I always wanted something like this. BTW, I'd like to propose an alternative: is-expression. It looks like this:
is-expression in contrast to if-let:
is-expression can be used anywhere deep inside of any expression, like this:
However, pattern element can be bound to variable only if is-expression is in &&-argument of if-cond, thus this is invalid (or probably a warning):
Somewhat similar is-expression is present in Kotlin programming language. Their
|
This comment has been minimized.
This comment has been minimized.
dobkeratops
commented
Jul 9, 2014
|
alternatives - could macros be beefed up to reduce rightward drift. Imagine if you could do this...
you could build macro forms that fit in more naturally, and have more ways of fighting rightward drift. Other than that... to me it does seem sensible to borrow ideas from swift - the language will be very widespread. And "if let ...." certainly makes sense to me coming from C++ where we can write |
This comment has been minimized.
This comment has been minimized.
|
+1 to the original proposal just as it is. I love it when someone submits the same idea I was going to. |
This comment has been minimized.
This comment has been minimized.
|
@stepancheg An |
This comment has been minimized.
This comment has been minimized.
stepancheg
commented
Jul 9, 2014
|
@kballard no,
|
This comment has been minimized.
This comment has been minimized.
|
@stepancheg Right, that's why I said "for matching alone". As I stated, I don't think it's appropriate to bind variables with an |
This comment has been minimized.
This comment has been minimized.
|
@stepancheg Would you really want to allow multiple if foo is A(ref s) || bar is B(ref t) {
...
}You have to bind every variable exactly once on every path through the conditions. The |
This comment has been minimized.
This comment has been minimized.
stepancheg
commented
Jul 9, 2014
|
@zwarich Multiple
|
This comment has been minimized.
This comment has been minimized.
|
@stepancheg So what does the grammar for your proposed construct look like? You have to add a new nonterminal that is neither an expression nor a pattern. |
This comment has been minimized.
This comment has been minimized.
stepancheg
commented
Jul 10, 2014
Sorry, didn't understand that part.
There's no special treatment of |
This comment has been minimized.
This comment has been minimized.
|
@stepancheg You haven't addressed my primary objection to I think this boils down to conflating an expression with a construct that has the power to bind a variable. Today the only ways to do that are with a declaration (an item or a slot (e.g. a let-binding)) or as part of a match arm, in which any bound variables cannot escape the associated |
This comment has been minimized.
This comment has been minimized.
stepancheg
commented
Jul 10, 2014
Well, I must admit, Probably, all those complex rules are not really needed in practice (or not needed in the first version). If so, I have much simpler, bikeshed, proposal: take yours proposal, and replace
with
It is almost as simple, as
|
This comment has been minimized.
This comment has been minimized.
|
@stepancheg That is certainly much cleaner than your previous approach, but here are the downsides:
All in all, I'd rather treat |
This comment has been minimized.
This comment has been minimized.
stepancheg
commented
Jul 10, 2014
That's a problem, thanks for pointing it out. |
This comment has been minimized.
This comment has been minimized.
Valloric
commented
Jul 10, 2014
|
This RFC is excellent. We need to improve Rust's ergonomics; lately it's been going down. |
This comment has been minimized.
This comment has been minimized.
nielsle
commented
Jul 10, 2014
|
Perhaps "is" could be replaced by @ in the version proposed by @stepancheg . That would be somewhat consistent with match statements and it would not require a new keyword.. if foo @ Some(x) {
doSomethingWith(x)
}EDIT: Changed => to @ |
This comment has been minimized.
This comment has been minimized.
hatahet
commented
Jul 10, 2014
|
I like the fact that it is easy to visually notice a |
This comment has been minimized.
This comment has been minimized.
|
@hatahet Well, the point of an As for HKT + If HKT + |
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
It turns out Swift also supports |
This comment has been minimized.
This comment has been minimized.
|
I've often wanted a keyword for the On Mon, Aug 25, 2014 at 7:18 PM, Kevin Ballard notifications@github.com
|
This comment has been minimized.
This comment has been minimized.
|
@kballard Interesting. I'm guessing it would be used like so?
I actually like this quite a bit, but (given that the discussion about @cmr As the simplest possible solution, we could even just make a special exception and allow |
This comment has been minimized.
This comment has been minimized.
|
@glaebhoerl Yeah that's how it would work.
|
This comment has been minimized.
This comment has been minimized.
|
+1 match maybe_x() {
Some(x) => { ... },
_ => match maybe_y() {
Some(y) => { ... },
_ => { ... },
},
}is way too noisy. |
This comment has been minimized.
This comment has been minimized.
|
@kballard Yeah, maybe. But weighed against the alternative of adding a new keyword? More economical to just make |
This comment has been minimized.
This comment has been minimized.
|
What new keyword? |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
|
@glaebhoerl Yes, and that's what the |
This comment has been minimized.
This comment has been minimized.
|
This was discussed in today's meeting. Concerns were brought up about how a simple AST rewrite can yield surprising results in, but it was also decided that an implementation not specc'd to be an AST rewrite would not be accepted. Due to lingering uncertainties, we decided to merge this with the caveat that it is all initially behind a feature gate. @kballard, can you update the RFC to reflect this? We also decided to postpone something like |
This comment has been minimized.
This comment has been minimized.
|
@alexcrichton Note about feature gate added. |
alexcrichton
merged commit d7a1dba
into
rust-lang:master
Aug 27, 2014
lilyball
deleted the
lilyball:if_let
branch
Aug 27, 2014
rust-highfive
referenced this pull request
Sep 24, 2014
Open
RFC: Add a matches!(expression, pattern) macro. #264
stepancheg
added a commit
to stepancheg/rust-protobuf
that referenced
this pull request
Dec 6, 2014
c-cube
referenced this pull request
Jun 9, 2015
Closed
add a `if let` construct to the language #194
This comment has been minimized.
This comment has been minimized.
Biluoshilang
commented
Oct 10, 2015
|
find no goods for it |
This comment has been minimized.
This comment has been minimized.
lilijreey
commented
Nov 14, 2017
|
I think it is so bad Idea. |
This comment has been minimized.
This comment has been minimized.
|
@lilijreey This landed over three years ago, well before Rust 1.0. It’s not going away. We like it, anyway, and it’s consistent with the rest of the language. (Your suggestion also leads to parsing difficulties, as |
This comment has been minimized.
This comment has been minimized.
najamelan
commented
Dec 7, 2017
|
I think making the syntax more concise is an important thing, since rust has a lot of boilerplate, which hinders readablility. Hence I was wondering why the following doesn't work: if let Some( content_type ) = res_in.headers.get( "Content-Type" )
&& let Ok ( parsed ) = content_type.parse()
{
out = out.with_header( hyper::header::ContentType( parsed ) );
}This expresses "Run this code block if all of these conditions are met". As opposed to: if let Some( content_type ) = res_in.headers.get( "Content-Type" ) {
if let Ok ( parsed ) = content_type.parse()
{
out = out.with_header( hyper::header::ContentType( parsed ) );
}}This isn't much longer to write, but what does it express? Confusion? To not be confusing to read, it should be written as: if let Some( content_type ) = res_in.headers.get( "Content-Type" )
{
if let Ok( parsed ) = content_type.parse()
{
out = out.with_header( hyper::header::ContentType( parsed ) );
}
}Which now is a lot more noisy. Seeing this type of construct in any other language indicates "poor condition logic probably hiding a bug", but in rust the language just obliges us to write stuff like this. The contrast between the two paradigms will grow if you have more conditions to chain up. |
This comment has been minimized.
This comment has been minimized.
|
@najamelan This RFC was finished over three years ago. Any changes such as you suggest are additions to the language which will need to be in the form of a new RFC. |
This comment has been minimized.
This comment has been minimized.
najamelan
commented
Dec 7, 2017
|
Ok, I'm quite new to rust, but if people think this has a chance of getting accepted, I'd be willing to write the RFC. |
This comment has been minimized.
This comment has been minimized.
|
If you want another channel for feedback before writing an RFC, use https://internals.rust-lang.org/ |
lilyball commentedJul 9, 2014
Summary
Introduce a new
if let PAT = EXPR { BODY }construct. This allows for refutable pattern matchingwithout the syntactic and semantic overhead of a full
match, and without the corresponding extrarightward drift. Informally this is known as an "if-let statement".
Motivation
Many times in the past, people have proposed various mechanisms for doing a refutable let-binding.
None of them went anywhere, largely because the syntax wasn't great, or because the suggestion
introduced runtime failure if the pattern match failed.
This proposal ties the refutable pattern match to the pre-existing conditional construct (i.e.
ifstatement), which provides a clear and intuitive explanation for why refutable patterns are allowed
here (as opposed to a
letstatement which disallows them) and how to behave if the pattern doesn'tmatch.
The motivation for having any construct at all for this is to simplify the cases that today call for
a
matchstatement with a single non-trivial case. This is predominately used for unwrappingOption<T>values, but can be used elsewhere.The idiomatic solution today for testing and unwrapping an
Option<T>looks likeThis is unnecessarily verbose, with the
None => {}(or_ => {}) case being required, andintroduces unnecessary rightward drift (this introduces two levels of indentation where a normal
conditional would introduce one).
The alternative approach looks like this:
This is generally considered to be a less idiomatic solution than the
match. It has the benefit offixing rightward drift, but it ends up testing the value twice (which should be optimized away, but
semantically speaking still happens), with the second test being a method that potentially
introduces failure. From context, the failure won't happen, but it still imposes a semantic burden
on the reader. Finally, it requires having a pre-existing let-binding for the optional value; if the
value is a temporary, then a new let-binding in the parent scope is required in order to be able to
test and unwrap in two separate expressions.
The
if letconstruct solves all of these problems, and looks like this:Detailed design
The
if letconstruct is based on the precedent set by Swift, which introduced its ownif letstatement. In Swift,
if let var = expr { ... }is directly tied to the notion of optional values,and unwraps the optional value that
exprevaluates to. In this proposal, the equivalent isif let Some(var) = expr { ... }.Given the following rough grammar for an
ifcondition:The grammar is modified to add the following productions:
The
expressionis restricted to disallow a trailing braced block (e.g. for struct literals) thesame way the
expressionin the normalifstatement is, to avoid ambiguity with the then-block.Contrary to a
letstatement, the pattern in theif letexpression allows refutable patterns. Thecompiler should emit a warning for an
if letexpression with an irrefutable pattern, with thesuggestion that this should be turned into a regular
letstatement.Like the
forloop before it, this construct can be transformed in a syntax-lowering pass into theequivalent
matchstatement. Theexpressionis given tomatchand thepatternbecomes a matcharm. If there is an
elseblock, that becomes the body of the_ => {}arm, otherwise_ => {}isprovided.
Optionally, one or more
else if(notelse if let) blocks can be placed in the samematchusingpattern guards on
_. This could be done to simplify the code when pretty-printing the expansionresult. Otherwise, this is an unnecessary transformation.
Due to some uncertainty regarding potentially-surprising fallout of AST rewrites, and some worries
about exhaustiveness-checking (e.g. a tautological
if letwould be an error, which may beunexpected), this is put behind a feature gate named
if_let.Examples
Source:
Result:
Source:
Result:
Source:
Result:
With the optional addition specified above:
Result:
Drawbacks
It's one more addition to the grammar.
Alternatives
This could plausibly be done with a macro, but the invoking syntax would be pretty terrible and
would largely negate the whole point of having this sugar.
Alternatively, this could not be done at all. We've been getting alone just fine without it so far,
but at the cost of making
Optionjust a bit more annoying to work with.Unresolved questions
It's been suggested that alternates or pattern guards should be allowed. I think if you need those
you could just go ahead and use a
match, and thatif letcould be extended to support those inthe future if a compelling use-case is found.
I don't know how many
matchstatements in our current code base could be replaced with thissyntax. Probably quite a few, but it would be informative to have real data on this.