Rfcs: Macroless mapping of patterns -> bool

Created on 15 Sep 2017  ·  9Comments  ·  Source: rust-lang/rfcs

The current if let $pattern = $expr {} functionality is great when you're de-structuring parts of the pattern for use in the if-scope. However, when matching without that need it can feel quite limited. I would suggest allowing macroless evaluation of certain pattern matches -> bool.

Current State

Example compile error:

enum Example {
    A(i32, &'static str),
    B(i32, bool),
}

let example = Example::A(123, "alex");
let always_do_it = true;

if let Example::A(.., "alex") = example || always_do_it {
    // compile error!
}

This makes sense when you can reference part of the pattern within the if-scope, but less so when it's a straight forward match.

The macroless way of mapping a pattern match to a boolean can currently be a little ugly.

if if let Example::A(.., "alex") = example { true } else { false} || always_do_it {
    // works, but ugly enough to have to split out
}

let examples: IntoIter<Example> = ...

// filter away the A-123 cases, difficult to read
examples.filter(|t| if let Example::A(123, ..) = *t { false } else { true });

New functionality

How about we allow pattern matches without a variable binding to be evaluated as bools.

let is_a_123: bool = let Example::A(123, ..) = example;

This would mean the above examples would start working / could be more readable.

if let Example::A(.., "alex") = example || always_do_it {
    // works now
}

examples.filter(|t| !let Example::A(123, ..) = *t) // nicer

Its worth noting explicitly that I would expect patterns with variable bindings to not be allowed

// compile error! variable bindings are not allowed in pattern -> bool evaluations
let is_a_123: bool = let Example::A(123, some_str) = example;

This means this functionality would have a clear distinction from the current if let $pattern = $expr {} usage.

Working example

I'd like this as macroless functionality built into the language. However, I can provide a simple macro for a working example.

macro_rules! iff { 
    (let $p:pat = $e:expr) => {{
        if let $p = $e { true } else { false }
    }};
}

let is_a_123: bool = iff!(let Example::A(123, ..) = example);

if iff!(let Example::A(.., "alex") = example) || always_do_it {}

examples.filter(|t| !iff!(let Example::A(123, ..) = *t));

These are not nice, but show the functionality working as close as we can currently get.

Comments

I think this would make a good addition to the current if let behaviour. So please fill me in on all the obvious stuff I've missed!

T-lang

Most helpful comment

All 9 comments

if let Example::A(.., "alex") = example || always_do_it {

This looks to me like if let Example::A(.., "alex") = (example || always_do_it) {

Bikeshed:

Contextual keyword matches:

let is_a_123: bool = example matches Example::A(123, ..);
if example matches Example::A(.., "alex") || always_do_it {}
examples.filter(|t| !(*t matches Example::A(123, ..)));

Or is matching + is not matching as contextual sentences 😆

This looks to me like if let Example::A(.., "alex") = (example || always_do_it) {

Hmmm, yes I suppose since the assignment operator has the least precedence it would seem like that. That's a big problem with the proposal, thanks for pointing that out.

Contextual keyword matches

I totally agree. But I didn't suggest this as I thought adding a new keyword to the language would be too difficult. The strength of enhancing let $pattern = $expr usage would be backwards compatibility.

I suppose the question now is: Can we do this in a backwards compatible way / without adding a new keyword?

Could the match keyword take on an additional use, similar to what you suggested?
It'll essentially become a binary operator with high precedence $expr match $pattern.

let is_a_123: bool = example match Example::A(123, ..);
if example match Example::A(.., "alex") || always_do_it {}
examples.filter(|t| !(*t match Example::A(123, ..)));

The strength of enhancing let $pattern = $expr usage would be backwards compatibility.

The problem with reusing the same syntactical pattern is that it's very weird for the refutability of a pattern to change the type of the let binding.

Ultimately I believe we need a better solution to the over-verbose:

if let $pattern = $expr { true } else { false }

I'm not sure this issue is helping though, as it isn't a good enough proposal and isn't going anywhere.

I while ago I submitted https://github.com/rust-lang/rfcs/pull/163 for adding a matches!($expression, $pattern) macro to the standard library. It was rejected because at the time there was no mechanism to add it without having it in the prelude. Maybe with Macros 2.0 it’s more doable now?

Indeed, and a macro/if-else is what I use now for this.

This was about getting the functionality into the language without macros or verbosity. This is something I still feel is worthwhile.

Linking #2260

Was this page helpful?
0 / 5 - 0 ratings

Related issues

torkleyy picture torkleyy  ·  3Comments

3442853561 picture 3442853561  ·  3Comments

rudolfschmidt picture rudolfschmidt  ·  3Comments

rust-highfive picture rust-highfive  ·  4Comments

Diggsey picture Diggsey  ·  3Comments