Matching variable values


#1

I asked on stackoverflow about idiomatic ways to use values computed at runtime as patterns in match expressions:

I have a value (byte) that I need to match against other values. Some of the values are fixed (b'0'..b'9'). Others are computed at runtime (c = some_function()).

My current solution is to use a fake variable and a if guard (i.e. k if (k == c)), but it does not look very nice to me. I tried using just c but that gets interpreted as a catch-all variable, rather than substituted for the value of c in that context.

Couldn’t the rules for matching inside match be changed to use the content of an already bound variable when it is used inside as a pattern?

That would allow me to write

let target_char = CHARS[i];

let msg = match byte {
    b'0'..b'9' => "numeric",
    target_char => "same char", // <-- I would had preferred this
    _ => "different char",
};

instead of

let target_char = CHARS[i];

let msg = match byte {
    b'0'..b'9' => "numeric",
    k if (k == target_char) => "same char",
    _ => "different char",
};

The former is more concise and it also looks simpler to me. Is the style in the second snippet forced “by design” or maybe Rust will change in the future to allow also the style in the first snippet?


#2

A few issues with this:

  • It adds a fourth meaning of an identifier in a pattern besides unit struct match, variable binding and match with a static. The case convention would also conflict with bindings, making it locally impossible to judge.
  • It makes match depend on a trait (PartialEq) to compare the equality
  • That dependency also means user code can run during matching without an explicit if clause, which so far is being avoided.
  • Exhaustiveness checking would have to consider it as a refutable pattern, which means a match on a identifier gets an third exhaustiveness mode (besides “can match and removes this case from all possibilities” and “always matches”)

All in all, it just adds an special case to match and makes it more complicated to reason about, which I think is a bad idea.

However, what might be a bit more viable is to extend match to a syntactic shortform for predicates like this:

let target_char = CHARS[i];

let msg = match byte {
    b'0'..b'9' => "numeric",
    if @ == target_char => "same char",
    _ => "different char",
};

Here, if expr( ... @ ... ) would be the predicate short form and desugar to TMP if expr (... TMP ... )

However, it would be also possible to implement either this or the == special case as a macro:

let target_char = CHARS[i];

let msg = eq_match!{ byte {
    b'0'..b'9' => "numeric",
    == target_char => "same char",
    _ => "different char",
}};

let msg = pshort_match!{ byte {
    b'0'..b'9' => "numeric",
    if @ == target_char => "same char",
    _ => "different char",
}};

#3

Following your suggestion, couldn’t the compiler translate

target_char => "same char"

into

TMP if TMP == target_char => "same char"

when target_char is locally bound, or into

_ => "same char"

when it is not bound?


#4

Also, I have some questions about the other issues:

What does “impossible to judge” mean exactly?

BTW, match with a static would become a special case of matching with a bound variable name.

Is it that bad? Ruby has the “case equality” operator === exactly for this.

If target_char implemented PartialEq, wouldn’t we be running user code anyway to compute target_char == k?


#5

But I think if you write a == it’s more explicit that you’re calling the relevant function.


#6

Allowing local variables to be used directly in matches is likely to lead to very confusing bugs where match arms are surprisingly skipped, e.g.

enum Foo {
    A(int), B(f64), C, D
}

let x = 1;
// ... lines of code ...

match foo {
    // trying to bind a new name `x`
    A(x) => println!("A: {}", x),
    B(y) => println!("B: {}", y),
    _ => println!("other")
}

If variables could be used as patterns, that code would basically be match foo { A(1) => ... and hence foo = A(2) would print other: very different to the intended behaviour. We already somewhat have this problem with statics and match, but the naming conventions help mitigate it; this is not the case with local variables.

(This is possibly along the lines of what @Kimundi meant by “impossible to judge”.)


#7

What if there was some special syntax that was shorthand for x if x == expr? For example, using an expression in backtics, a la Scala.

I think that addresses all of the concerns. Exhaustiveness would require a catch all case.