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 { () }

is in form 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)

yet adds bindings to the containing scope (including shadowing) Ambiguity when rhs of = is in form if { () }

is in form 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)

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)

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