Runoff poll between two most popular options below!
I think it’s time to try bringing around the postponed RFC #1303, refutable let (RFC tracking issue) for consideration again. I’ve started drafting out what the new RFC might look like, and want to gather some feedback on both subjective opinion and any concrete benefits/drawbacks to the different possible syntaxes. I’ll keep the OP as-up-to-date as I can with examples. For some context, I wrote a blog post examining the original RFC and recently posted a new syntax idea to r/rust, which got some discussion. If all goes well, one syntax should emerge as a “better” choice which the actual RFC text can start with. If I’ve missed a possibility, please suggest it below, along with any drawbacks I might have missed.
I’m also interested if you can come up with any motivating examples. I’m convinced that this is a useful feature, given the limitations laid out in the “do nothing” choice, but most small examples I can come up with have another refactored representation that is better, using the high-level data manipulation fn.
The order of syntaxes below is roughly arbitrary with some grouping of similar choices. <keyword>
refers to a bikeshedable new (potentially contextual) keyword when used. We start with some opt: Option<T>
, and want to bind Some(val) = opt
in the containing scope, otherwise log!
some information and bail!
(using error-chain
or failure
).
Just use combinators
(Do nothing)
let Ok(x) = opt.ok_or_else(|| {
log!();
Err()
})?;
Known problems:
- Doesn’t work for types that haven’t built up a library of useful higher-order fn
- Introduces a new closure context, meaning
?
,return
,break
, andcontinue
can’t be used for control flow
Just use match
(Do nothing)
let x = match opt {
Some(x) => x,
_ => {
log!();
bail!()
}
};
Known problems:
- Verbose, requires repeating information (especially if more than one value is bound in the pattern)
- Does not enforce divergence of the error case (this can be fixed the way the desugaring would work, by introducing a
let _: ! = { <block> }
context, but this adds more boilerplate and another level of indentation, which the point of this construct is to reduce needed indentation.
let ... else
let Some(val) = opt else {
log!();
bail!()
}
Known problems:
- Ambiguity when rhs of
=
is in formif { () }
- Expect regular binding by prefix, get something extra at the end
<keyword> let ... else
guard let Some(val) = opt else {
log!();
bail!()
}
Known Problems:
- Doesn’t start with
let
yet adds bindings to the containing scope (including shadowing) - Ambiguity when rhs of
=
is in formif { () }
- Requires new keyword
<keyword> let
unless let Some(val) = opt {
log!();
bail!()
}
Known Problems:
- Doesn’t start with
let
yet adds bindings to the containing scope (including shadowing) - Requires new keyword
- Human ambiguity around blocks in rhs the same as exist for
if let
else let
else let Some(val) = opt {
log!();
bail!()
}
Known Problems:
-
Doesn’t start with
let
yet adds bindings to the containing scope (including shadowing) -
Human ambiguity around blocks in rhs the same as exist for
if let
-
Human ambiguity when preceded by an
if
orif let
statement – what should the following do?let x = 1; let y = Some(2); let z = Some(3); if let Some(x) = y { println!("{}", x); } else let Some(x) = y { println!("{}", x); } println!("{}", x);
Answer
[error] else let must diverge | | else let Some(x) = y { | ^^^^^^^^ note = expected type `!` note = found type `()`
(Where it points picked arbitrarily) What about when you add a
panic!()
to theelse let
block?Answer
[warning] unused variable | | let x = 1; | ^ note = shadowed here: | | else let Some(x) = y { | ^ [build successful, running] 2 3
I believe this ambiguity will go away with time as programmers learn to parse the
else let
syntax, similar to the adjustment period adoptingif let
or?
.
let ... <keyword>
let Some(val) = opt or else {
log!();
bail!()
}
Known Problems:
- Expect regular binding by prefix, get something extra at the end
- Requires new contextual keyword
<keyword> ... <keyword>
(or with the let
)
guard Some(val) = opt or else {
log!();
bail!()
}
Known Problems:
- Doesn’t start with
let
yet adds bindings to the containing scope (including shadowing)
if !let
if !let Some(val) = opt {
log!();
bail!()
}
Known Problems:
-
if let
does not introduce bindings to the parent scope, this does (including shadowing) - makes a
let
binding feel like abool
-valued expression
let match
/ match let
let match Some(val) = opt {
_ => {
log!();
bail!()
}
}
Known Advantages:
- Allows you to easily handle destructure the error case
- Strictly more expressive than the other proposed options
Known Problems:
- Heavier than any of the other options
- Match syntax could be added into other options even without the
match
keyword
- Don’t do anything
-
let ... else
(known ambiguity) -
<keyword> let ... else
(known ambiguity) <keyword> let
else let
let ... <keyword>
<keyword> ... <keyword>
if !let
0 voters