-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC for if let
expression
#160
Conversation
I like this! |
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, 👍 from me. |
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 |
Just a nitpick: If optVal is an option, then you can also do optVal.map( |x|
do_something_with(*x)
); |
@nielsle Only if using a closure isn't a problem, which it often can be, and if you have no need for an |
@sinistersnare What do you mean, a time in this language where we do not need/want sugar? |
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. |
To me it seems the benefit of this is essentially not having to write the
|
@bachm, I think the main benefit is that there is no extra nesting and indentation here. Your The proposal looks great, +1 from me. |
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
|
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 |
+1 to the original proposal just as it is. I love it when someone submits the same idea I was going to. |
@stepancheg An |
@kballard no,
|
@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 |
@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 |
@zwarich Multiple
|
@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. |
Sorry, didn't understand that part.
There's no special treatment of |
@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 |
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
|
@stepancheg That is certainly much cleaner than your previous approach, but here are the downsides:
All in all, I'd rather treat |
That's a problem, thanks for pointing it out. |
This RFC is excellent. We need to improve Rust's ergonomics; lately it's been going down. |
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 @ |
I like the fact that it is easy to visually notice a |
@hatahet Well, the point of an As for HKT + If HKT + |
|
@glaebhoerl Yeah that's how it would work.
|
+1 match maybe_x() {
Some(x) => { ... },
_ => match maybe_y() {
Some(y) => { ... },
_ => { ... },
},
} is way too noisy. |
@kballard Yeah, maybe. But weighed against the alternative of adding a new keyword? More economical to just make |
What new keyword? |
@glaebhoerl Yes, and that's what the |
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 |
Also fix a couple of typos.
@alexcrichton Note about feature gate added. |
(I'd like Rust to have more generic `is` expression: rust-lang/rfcs#160 (comment) )
find no goods for it |
I think it is so bad Idea. |
@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 |
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. |
@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. |
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. |
If you want another channel for feedback before writing an RFC, use https://internals.rust-lang.org/ |
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.
if
statement), which provides a clear and intuitive explanation for why refutable patterns are allowed
here (as opposed to a
let
statement 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
match
statement 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 let
construct solves all of these problems, and looks like this:Detailed design
The
if let
construct is based on the precedent set by Swift, which introduced its ownif let
statement. In Swift,
if let var = expr { ... }
is directly tied to the notion of optional values,and unwraps the optional value that
expr
evaluates to. In this proposal, the equivalent isif let Some(var) = expr { ... }
.Given the following rough grammar for an
if
condition:The grammar is modified to add the following productions:
The
expression
is restricted to disallow a trailing braced block (e.g. for struct literals) thesame way the
expression
in the normalif
statement is, to avoid ambiguity with the then-block.Contrary to a
let
statement, the pattern in theif let
expression allows refutable patterns. Thecompiler should emit a warning for an
if let
expression with an irrefutable pattern, with thesuggestion that this should be turned into a regular
let
statement.Like the
for
loop before it, this construct can be transformed in a syntax-lowering pass into theequivalent
match
statement. Theexpression
is given tomatch
and thepattern
becomes a matcharm. If there is an
else
block, that becomes the body of the_ => {}
arm, otherwise_ => {}
isprovided.
Optionally, one or more
else if
(notelse if let
) blocks can be placed in the samematch
usingpattern 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 let
would 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
Option
just 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 let
could be extended to support those inthe future if a compelling use-case is found.
I don't know how many
match
statements 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.