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, and continue 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 form if { () }
- 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 form if { () }
- 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 or if 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 the else 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 adopting if 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 a bool-valued expression
let match / match let
let match Some(val) = opt {
_ => {
log!();
bail!()
}
}
Suggested here
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