Why doesn't match support expressions in branches?

For example, the following code doesn't compile. Is there any reason behind this?

let x = 1;
match x {
    0 + 1 => "how",
    _ => "are",
}
2 Likes

Expression evaluation is undecidable, so there would be no way for the compiler to enforce exhaustive (or even terminating) matches.

The following if/else code block works. So besides simpler syntax, what is the advantage of match over if/else?

let x = 1;
if x == 0 + 1 { 
    "how" 
} else { 
    "are" 
}
1 Like

For a two-way branch that only uses logical or relational operators, if is probably more appropriate. match shines when you need to actually match against complex, nested patterns, and/or extract associated values from enums. For example, this:

match some_enum_value {
    Variant1(value1) => do_stuff(value1),
    Variant2(value2) => do_something(value2),
    Variant3(value3) => do_something_else(value3),
}

is less verbose than

if let Variant1(value1) = some_enum_value {
    do_stuff(value1)
} else if let Variant2(value2) = some_enum_value {
    do_something(value2)
} else if let Variant3(value3) = some_enum_value {
    do_something_else(value3)
}

And enormously better than

if let Variant1(value1) = some_enum_value {
    do_stuff(value1)
} else if let Variant2(value2) = some_enum_value {
    do_something(value2)
} else if let Variant3(value3) = some_enum_value {
    do_something_else(value3)
} else {
    println!("This branch should not be reached. Ask devs if you found this log printed");
}
4 Likes

Why does the following code works then? The value of y is not determined at compile time, right?

let x = 1;
fn f(x: i32) -> i32 {
    x
}
let y = f(1);
match x {
    y => "how",
    _ => "are",
}

If you compile this code, you'll see various compile warnings.

   Compiling playground v0.0.1 (/playground)
warning: unreachable pattern
 --> src/main.rs:9:9
  |
8 |         y => "how",
  |         - matches any value
9 |         _ => "are",
  |         ^ unreachable pattern
  |
  = note: `#[warn(unreachable_patterns)]` on by default

warning: unused variable: `y`
 --> src/main.rs:6:9
  |
6 |     let y = f(1);
  |         ^ help: if this is intentional, prefix it with an underscore: `_y`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `y`
 --> src/main.rs:8:9
  |
8 |         y => "how",
  |         ^ help: if this is intentional, prefix it with an underscore: `_y`

warning: 3 warnings emitted

    Finished dev [unoptimized + debuginfo] target(s) in 1.55s
     Running `target/debug/playground`

The _ => arm is unreachable, the let y variable is unused, and another variable y in the y => branch also is unused.

The left side of the => in the match expression is pattern. y is an identifier pattern which is irrefutable pattern and creates a variable holding the matched value.

match x {
    y => "how",
    _ => "are",
}

In this match expression the value x is first matched over the y pattern. It's irrefutable means it always succeed, the value of the x is now bind to the new variable y and its arm "how" is evaluated.

8 Likes

I see. match does pattern matching rather than value comparison. It is not equivalent to the C-like switch statement. However, if I have to do multiple value comparisons (can only be calculated using expressions), is there a simple way rather than using if statement?

1 Like

Yes, you can do that using "match guards":

match x {
    val if val == 0 + 1 => { ... },
    val if val == f(3) => { ... },
    val => { ... },
}
4 Likes

That's cool! But it is even more verbose compared to an if/else statement.

Why don't you just use an if, then?

1 Like

Only for such a simple example. If you're matching as well, it works great:

match x {
    Some(val) if val == 0 + 1 => { ... },
    Some(val) if val == f(3) => { ... },
    Some(val) => { ... },
    None => { ... },
}

It also saves you from changing indentation, which makes the scope clearer.

This is supported on nightly using const blocks:

let x = 1;
match x {
    const { 0 + 1 } => "how",
    _ => "are",
}

It is syntactically disallowed because or-patterns, struct patterns, and a few other things are ambiguous between expressions and patterns. The const unambiguously signifies that the contents should be parsed as an expression and any variables inside it should be coming from the surroundings, and are not new variable bindings.

You can also do this on stable but you need an auxiliary const definition:

let x = 1;
const Z: i32 = 0 + 1;
match x {
    Z => "how",
    _ => "are",
}
5 Likes

How about:

    match "whatever" {
        _ if a == b + c => "Yes",
        _ if b == c - a => "No",
        _ if c == a * b => "May be",
        ...
        _ => "WTF",
    };

Saves all those pesky } else {.

I thought Z matches any pattern.

1 Like

If it is a constant in scope, it resolves to the constant. Otherwise it is treated like a variable name. The relevant section from the reference is identifier patterns:

Path patterns take precedence over identifier patterns. It is an error if ref or ref mut is specified and the identifier shadows a constant.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.