# \[Pre-Pre-RFC\] Reviving Refutable Let \[Runoff\]

**URL:** <https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672>\
**Category:** language design\
**Created:** [January 31, 2018, 4:41am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672 "2018-01-31T04:41:00Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [January 31, 2018, 4:41am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/1 "2018-01-31T04:41:00Z")

</div>

[Runoff poll between two most popular options below!](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-poll/6672/49?u=cad97)

I think it’s time to try bringing around the [postponed RFC #1303, refutable let](https://github.com/rust-lang/rfcs/pull/1303) ([RFC tracking issue](https://github.com/rust-lang/rfcs/issues/373)) 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](https://dev.to/cad97/refutable-let-and-rust-in-2018-4l3k) and [recently posted a new syntax idea to r/rust, which got some discussion](https://redd.it/7tuitz). 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)

```rust
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)

```rust
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`

```rust
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`

```rust
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`

```rust
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`

```rust
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 ... <keyword>`

```rust
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`)

```rust
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`

```rust
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`

```rust
let match Some(val) = opt {
    _ => {
        log!();
        bail!()
    }
}

```

[Suggested here](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-poll/6672/4)

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

_Poll ([view on site](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/1))_

---

<div class="post-metadata">

**Author:** ![scottmcm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/scottmcm/32/2355_2.png) [@scottmcm](https://internals.rust-lang.org/u/scottmcm)\
**Post date:** [February 1, 2018, 4:19am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/2 "2018-02-01T04:19:32Z")

</div>

An advantage for the ones with a new keyword: it makes the “it must diverge” part really obvious up front, and works with non-let cases. I think the latter would be nice especially for compound conditions, so that you don’t need to demorgan in your head. Like `unless a > 0 || b > 0 { return None; }`

Also, I suggest using a more complex example to make the “you have to repeat the variable names three times” more obviously annoying. Like in the below, you’d need to repeat `source_info`, `lvalue`, `lhs`, and `rhs`:

```rust
    unless let Statement {
        source_info,
        kind: StatementKind::Assign(lvalue, Rvalue::BinaryOp(_, lhs, rhs)),
    } = bin_statement
    {
        continue;
    };

```

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [February 1, 2018, 5:14am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/3 "2018-02-01T05:14:58Z")

</div>

That is a very good example; I used a shorter one for the demonstration just to try to keep the length manageable.

I just want to point out that in theory `else $expr { diverge; }` (so the preexisting keyword option) could still work, as `else` cannot be followed by an expression in the current grammar, only a block. But that is a good argument against the forms that use a leading new keyword without `let`.

---

<div class="post-metadata">

**Author:** ![canndrew](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/canndrew/32/1676_2.png) [@canndrew](https://internals.rust-lang.org/u/canndrew)\
**Post date:** [February 1, 2018, 5:39am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/4 "2018-02-01T05:39:27Z")

</div>

I’d like to add another option to that list:

```rust
let match Variant0(x) = expr {
    Variant1(..) => ...,
    Variant2(..) => ...,
    ...
}
// `x` is in-scope here

```

I think this is preferable to `let ... else` for a couple of reasons. Firstly, it’s just as powerful since you can use a single match branch to blindly capture all possibilities other than the pattern you were looking for. eg:

```rust
let match Foo(x) = expr {
    _ => return,
}

```

Secondly, when you test to see whether `expr` matches your patten you’re already doing a match on it. So it makes sense to have access to the result of that match in the “else” clause. Otherwise people will end up writing code like this:

```rust
let Ok(x) = my_result else {
    let err = my_result.unwrap_err(); // ewww! unwrap!
    ...
}

```

**Edit:** Although maybe `match let` would be better than `let match`. That would make it look like `if let` and `while let` although that might be a bad thing since `match let` binds variables in the code _after_ the block, not in the block.

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [February 1, 2018, 5:54am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/5 "2018-02-01T05:54:05Z")

</div>

I actually really like this idea of providing match arms within the diverging block. Though this could be provided without the `match` keyword provided, doing so clarifies intent.

I personally don’t have a real opinion on `let match` versus `match let`. The order of `match let` is more consistent to `if let`/`while let`, but since this adds its binding to the containing scope, starting with `let` seems beneficial as well.

I’ve added the option to the OP, but I’m not allowed to edit the poll (to avoid invalidating results).

---

<div class="post-metadata">

**Author:** ![rpjohnst](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rpjohnst/32/9524_2.png) [@rpjohnst](https://internals.rust-lang.org/u/rpjohnst)\
**Post date:** [February 1, 2018, 6:12am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/6 "2018-02-01T06:12:07Z")

</div>

Interesting idea- removes some rightward drift at the cost of adding some in other cases. I wonder if there’s a good syntax for making it optional- something like this maybe?

```
unless let Foo(x) = some_foo() match {
    Bar(y) => return ...
};

```

Or

```
let Foo(x) = some_foo() else match {
    Bar(y) => return ...
};

```

The second one plays into the idea of `else match` that I think has popped up before.

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [February 1, 2018, 7:52am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/7 "2018-02-01T07:52:15Z")

</div>

Another approach is to lean on hypothetical `is` operator: [https://github.com/rust-lang/rfcs/issues/929#issuecomment-285602496](https://github.com/rust-lang/rfcs/issues/929#issuecomment-285602496)

```rust
if some(foo) !is Foo(x) {
    // no x in scope here
}

// x in scope here

```

cc @petrochenkov

---

<div class="post-metadata">

**Author:** ![F001](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/f001/32/3022_2.png) [@F001](https://internals.rust-lang.org/u/F001)\
**Post date:** [February 1, 2018, 8:16am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/8 "2018-02-01T08:16:42Z")

</div>

I love @petrochenkov 's suggestion a lot. We really need a “subject-verb-object” structure to express “expression-matches-pattern”. `is` is less verbose than `matches` , I think it is a great option on table.

---

<div class="post-metadata">

**Author:** ![kennytm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kennytm/32/161_2.png) [@kennytm](https://internals.rust-lang.org/u/kennytm)\
**Post date:** [February 1, 2018, 11:59am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/9 "2018-02-01T11:59:15Z")

</div>

Details about `$expr is $pat` (or `let $pat = $expr` as a boolean expression) can be found in discussions of [RFC #2260](https://github.com/rust-lang/rfcs/pull/2260). One big problem of `is` is how bindings are made. Without name bindings it is not sufficient to replace `let … else`. 2260 compare `is` with several other languages (C#, Kotlin, TypeScript), but:

- Swift’s `$pat ~= $expr` does not support name binding

- In [C# 7’s `$expr is $pat`](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/is#pattern-matching-with-is), a declaration statement will be inserted before the current statement, i.e. the following two statements are equivalent:

- Kotlin’s smart cast (`$expr is $ty`) and TypeScript’s type guards (e.g. `$expr instanceof $ty`) is not used for pattern matching (destructuring). They alter the type of `$expr` so that type-specific fields will become available.

There is an attempt to create a precise scoping rule in [https://github.com/rust-lang/rfcs/pull/2260#issuecomment-354525996](https://github.com/rust-lang/rfcs/pull/2260#issuecomment-354525996).

---

<div class="post-metadata">

**Author:** ![nikomatsakis](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/nikomatsakis/32/5410_2.png) [@nikomatsakis](https://internals.rust-lang.org/u/nikomatsakis)\
**Post date:** [February 1, 2018, 2:53pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/10 "2018-02-01T14:53:21Z")

</div>

I’m not sure what’s the best option here (though I like the `let match` family of options), but I definitely feel like this pattern comes up a lot, and it’d be nice to have a good fix for it.

---

<div class="post-metadata">

**Author:** ![Diggsey](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/diggsey/32/6311_2.png) [@Diggsey](https://internals.rust-lang.org/u/Diggsey)\
**Post date:** [February 1, 2018, 4:40pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/11 "2018-02-01T16:40:28Z")

</div>

I think we should consider syntaxes which act purely as modifiers on patterns (converting a refutable pattern into an irrefutable pattern).

One possible example:

```rust
let {
    panic!()
} unless Some(foo) = bar();

```

This adds a new form of pattern:

```rust
<irrefutable_pat> = <expr:!> unless <refutable_pat>

```

Would also be usable as:

```rust
match x {
    A => println!("A"),
    B => println!("B"),
    panic!("unexpected") unless C => println!("C")
}

```

---

<div class="post-metadata">

**Author:** ![CAD97](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/cad97/32/3460_2.png) [@CAD97](https://internals.rust-lang.org/u/CAD97)\
**Post date:** [February 1, 2018, 8:25pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/12 "2018-02-01T20:25:37Z")

</div>

Interesting concept. What makes

```rust
match e {
    A => "A",
    B => "B",
    unreachable!() unless C => "C",
}

```

any better than

```rust
match e {
    A => "A",
    B => "B",
    C => "C",
    _ => unreachable!(),
}

```

though? Correct me if I’m wrong, but I can’t think of any location other than `let` where a single pattern is used rather than allowing one of many to be selected, and a `_` arm serving to make it exhaustive.

I’m pretty sure that specific syntax would require using a full keyword instead of a contextual one as well, in order to avoid ambiguities.

---

<div class="post-metadata">

**Author:** ![kennytm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kennytm/32/161_2.png) [@kennytm](https://internals.rust-lang.org/u/kennytm)\
**Post date:** [February 2, 2018, 8:43am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/13 "2018-02-02T08:43:27Z")

</div>

> [@matklad](#):
>
> Another approach is to lean on hypothetical is operator: [Support && in if let expressions · Issue #929 · rust-lang/rfcs · GitHub](https://github.com/rust-lang/rfcs/issues/929#issuecomment-285602496)
> 
> ```rust
> if some(foo) !is Foo(x) {
> // no x in scope here
> }
> // x in scope here
> 
> ```

This doesn't work, you also need the `if` block to be diverging, otherwise we can have an uninitialized `x`

```rust
if foo !is Some(x) {
    // can't use 'x' here, good.
    // but we choose to fallthrough
}
let y = x; // the `x` here should be invalid

```

Unless you only permit `return`/`break`/`continue`, knowing whether an expression is diverging (e.g. has type `!`) requires type checking, which would be too late to determine if `x` should be in scope or not.

---

<div class="post-metadata">

**Author:** ![petrochenkov](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/petrochenkov/32/2784_2.png) [@petrochenkov](https://internals.rust-lang.org/u/petrochenkov)\
**Post date:** [February 2, 2018, 9:09am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/14 "2018-02-02T09:09:44Z")

</div>

> [@kennytm](#):
>
> which would be too late to determine if x should be in scope or not.

The idea is that `x` is always in scope (from name resolution point of view), but it may be potentially uninitialized and if it's uninitialized, then access to it results in an error reported by some control/data flow pass performed after type checking.  
(I never proposed bindings from `is` to be in scope outside of their statement though, so in my scheme `is` is not a solution for the `let ... else` problem.)

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [February 2, 2018, 9:16am UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/15 "2018-02-02T09:16:00Z")

</div>

> [@kennytm](#):
>
> This doesn’t work, you also need the if block to be diverging, otherwise we can have an uninitialized x

I was under the impression that refutable let's else branch is required to be diverging under any proposal? Like, this is the main idea behind the feature: you handle exceptional cases by returning, and deal with a happy case using straight-line code 🙂

---

<div class="post-metadata">

**Author:** ![kennytm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kennytm/32/161_2.png) [@kennytm](https://internals.rust-lang.org/u/kennytm)\
**Post date:** [February 2, 2018, 12:26pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/16 "2018-02-02T12:26:06Z")

</div>

Yes, refutable let requires the else branch to be diverging, but a simple `is` expression places no such requirement.

---

<div class="post-metadata">

**Author:** ![matklad](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/matklad/32/12266_2.png) [@matklad](https://internals.rust-lang.org/u/matklad)\
**Post date:** [February 2, 2018, 12:47pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/17 "2018-02-02T12:47:31Z")

</div>

Hm, it does places such requirement? Like, you’ll get “variable is unitialized” error at compile time if there’s no divergence, and everything would be ok if there is divergence.

---

<div class="post-metadata">

**Author:** ![kennytm](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kennytm/32/161_2.png) [@kennytm](https://internals.rust-lang.org/u/kennytm)\
**Post date:** [February 2, 2018, 4:12pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/18 "2018-02-02T16:12:34Z")

</div>

😕 I’d expect it follow Swift’s `guard let ... else` which requires a diverging block.

IIRC there is also suggestion allowing it to be non-diverging but still initializes the `x`:

```rust
let Some(x) = foo else { Some(4) };
// equivalent to `x = foo.unwrap_or(4);`

```

I didn’t remember suggestions making it conditionally uninitialized though 😅

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [February 2, 2018, 4:41pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/19 "2018-02-02T16:41:59Z")

</div>

What I don’t like about proposed variants of the syntax is that they look too much like a multi-line value for a regular `let`:

```rust
let Some(val) = {
   some();
   code()
};

```

is visually quite similar to the opposite case:

```rust
let Some(val) = val else {
   some();
   code()
};

```

And the `let {…} unless <pattern>` is so far the clearest one in this regard 👍 (the different meaning of the block is easy to see by the lack of the pattern in `let {`.

I also propose a variant: `do {} unless let <pattern>`.

---

<div class="post-metadata">

**Author:** ![rpjohnst](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/rpjohnst/32/9524_2.png) [@rpjohnst](https://internals.rust-lang.org/u/rpjohnst)\
**Post date:** [February 2, 2018, 6:34pm UTC](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672/20 "2018-02-02T18:34:43Z")

</div>

I really don’t like putting the pattern at the end of the diverging block, because it’s the condition that determines whether it will execute. Would a different initial keyword be a strong enough marker? (e.g. `unless let Some(val) = val { ... }`)

[Next page](https://internals.rust-lang.org/t/pre-pre-rfc-reviving-refutable-let-runoff/6672.md?page=2)
