Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Or-patterns

"Or-patterns" are the patterns that look like $pat | $pat. They are tricky when they have bindings and when they're under match guards. We desugar them in this step.

We first "normalize" or-patterns by moving any nested | to the outside of a pattern, e.g. (0 | 1, 2 | 3) becomes (0, 2) | (0, 3) | (1, 2) | (1, 3) (see Discussion below about the combinatorial explosion). This expansion is done left-to-right.

Inside let chains, we simply turn let $pat1 | $pat2 = $expr into let $pat1 = $expr || let $pat2 = $expr using Extended Let Chains.

Inside matches, we encode the non-tree-like control-flow directly:

#![allow(unused)]
fn main() {
match $place {
    $pat1 | $pat2 if $guard => $arm,
    $remaining_arms
}

// becomes:
'match_end: {
    let x1_; // declare the bindings bound in the patterns, renamed to avoid shadowing.
    ..
    let xn_;
    'arm: {
        break 'match_end (match $place {
            $pat1 if $guard => {
                x1_ = x1;
                ..
                xn_ = xn;
                break 'arm;
            },
            $pat2 if $guard => {
                x1_ = x1;
                ..
                xn_ = xn;
                break 'arm
            },
            $remaining_arms
        });
    }
    $arm_ // modified to use `xi_` instead of `xi`
}
}

After this step, patterns don't involve |.


Discussion

Drop order

The let-chain desugaring is actually incorrect wrt drop order: or-patterns declare their bindings in the order given by the first subpattern (Reference), but our desugaring will drop them in the order of the alternative that succeeds.

I expect that design choice to prove to be trouble when mixing or-patterns and if-let guard patterns however, so I'd actually propose we make or-patterns drop their bindings in the order of the alternative that succeeded. This would make the proposed desugaring correct.

Running guards several times

Note an interesting property that this desugaring makes clear: a single match guard may run several times. This can be observed, e.g.:

#![allow(unused)]
fn main() {
let mut guard_count = 0;
match (false, false) {
    (a, _) | (_, a) if { guard_count += 1; a } => {}
    _ => {}
}
assert_eq!(guard_count, 2); // succeeds

// is equivalent to:
let mut guard_count = 0;
match (false, false) {
    (a, _) if { guard_count += 1; a } => {}
    (_, a) if { guard_count += 1; a } => {}
    _ => {}
}
assert_eq!(guard_count, 2); // succeeds
}

See also this fun test.

Combinatorial explosion

This desugaring has the benefit of simplicity but two big drawbacks: it duplicates user code (the match guards), and more importantly causes combinatorial explosion. For example, (true|false, true|false, true|false, true|false) if $guard desugars to 16 patterns and 16 copies of the guard code.

A more robust approach could be to give an index to each sub-pattern and branch/loop on these indices to know the right bindings to use/number of times to run a guard. For example, using guard patterns:

#![allow(unused)]
fn main() {
match $place {
    ($a | $b, Some($c | Ok($d | $e))) if $guard => $arm,
    _ => {}
}

// could become something like:
macro_rules! cond_pat {
    // A conditional pattern: behaves like `$p` if `$i == 0` or like `$q` if `$i == 1`
    ($i:expr, $p:pat | $q:pat) => {
        (place pl if $i == 0 && let $p = pl)
        | (place pl if $i == 1 && let $q = pl)
    };
}
'success: for i in 0..=1 {
    for j in 0..=1 {
        let max_k = if j == 0 { 0 } else { 1 };
        for k in 0..=max_k {
            if let (
                cond_pat!(i, $a | $b),
                Some(cond_pat!(j, $c | Ok(cond_pat!(k, $d | $e)),
            ) = $place && guard {
                $arm
                break 'success
            }
        }
    }
}
}