Should `let match (..) { .. } else` compile?

I wrote a let .. else pattern but then converted the inner check to a match expresion but was surprised it won't compile. Is this intended?

Here reduced:

fn does_not_compile() -> Option<usize> {
    struct Inner {
        value: usize,
    }
    let a = Some(Inner { value: 1337 });

    let Some(b) = match a {
        Some(inner) => Some(inner.value),
        _ => None
    } else {
        return None;
    };

    Some(b)
}

fn compiles_when_we_add_parentheses() -> Option<usize> {
    struct Inner {
        value: usize,
    }
    let a = Some(Inner { value: 1337 });

    let Some(b) = (match a {
        Some(inner) => Some(inner.value),
        _ => None
    }) else {
        return None;
    };

    Some(b)
}

compiler says:

error: right curly brace `}` before `else` in a `let...else` statement not allowed
    --> libs/universe/src/entity.rs:2026:5
     |
2026 |     } else {
     |     ^
     |
help: wrap the expression in parentheses
     |
2023 ~     let Some(b) = (match a {
2024 |         Some(inner) => Some(inner),
2025 |         _ => None
2026 ~     }) else {
     |

Yes. Not every expression is supported on the right-hand side of a let-statement with an else block, most notably none that end with }, which match expressions are. Grouped expressions, i.e. (...), do not end with a curly brace,[1] so they are fine, making your second function compile.


  1. Nor are they LazyBooleanExpressions, which is the other type of expression, besides expressions ending with }, not supported by let-else statements. ↩︎

8 Likes

For completeness then, why is it intended?

Purely speculative guess, but I think to avoid ambiguities like this:

let x =
    if a { b }
    else { c } // does it belong to the `let` or `if` expression?
    else { d }

In the example above if may or may not have else branch. For example, the final expression could be a unit value, in which case else-of-if part is optional.

2 Likes

The RFC has this to say about the design decision:

While allowing e.g. if {} else {} directly in the expression position is technically feasible this RFC proposes it be disallowed for programmer clarity so as to avoid ... else {} else {} situations as discussed in the drawbacks section. Boolean matches are not useful with let-else and so lazy boolean expressions are disallowed for reasons noted in future-possibilities. These types of expressions can still be used when combined in a less ambiguous manner with parentheses, which is allowed under the two expression restrictions. Invisible groupings from macros expansions are also allowed, however macro expansion representations to humans should include parentheses around the expression output in this position if it ends in a } where possible (or otherwise show the invisible grouping).

So expressions ending with } are disallowed to avoid ambiguity of if { ... } else { ... } expressions. And lazy boolean expressions are (currently) disallowed in case || is added as an operand to pattern matching (I don't know if this is actively discussed somewhere), but in the discussion leading to the let-else RFC, they kept the option open.

3 Likes

if/else is fine on its own; adding else make the last one part of let..else and must diverge.

I don't see it ambiguous. Maybe I am falling into the ambiguity, or is it just less readable? Or that it may be confusing for some coders (but hardly ambiguous?) ?

It's about readability, i.e. there is no syntactical reason why if-else should be disallowed in let-else statements. But I'd still say let x = if a { b } else { c } else { return; }; is ambiguous[1] for a person reading the source code, even if it isn't for the compiler. You'd have to know the last else belongs to the let statement and not the if expression, which I agree with the authors of the RFC, is not ideal and best to be disambiguated by wrapping expressions ending with a } in (...).


  1. Maybe confusing would be the better word? ↩︎

3 Likes

The distinction is between

let () = (if c {}) else {};

and

let () = (if c {} else {});

which is ambiguous without the parens.

2 Likes

I thought it always required a complete if else and would never interpret as standalone else and so 1st case would be invalid.

But should test better then. Interesting.

Well it's more that nobody ever actually writes this

let () = if c { foo() };

because they just write

if c {
    foo();
}

but it's legal to put the let there too, because else-less if is an expression too.

1 Like

I didnt know () could be a variable like you put there, unless it is not.

It's a pattern.

1 Like