Rfcs: Proposal for a macro like `matches!()` that maps extracted value to `Option`

Created on 18 Jul 2020  路  4Comments  路  Source: rust-lang/rfcs

After the inclusion of the matches macro into std, there are still a lot of similar use cases that it can't be used with:
Those that require extracting a value like this:

    match param {
        ParamSelector::Int(_, TrackParam::Ext(name)) => Some(name),
        _ => None,
    }

These patterns occur frequently (or variations with if-let).
There is currently no concise one-line way to write this.
So I adapted the matches macro to work for these use cases:

#[macro_export]
macro_rules! match_map {
    ($expression:expr, $( $pattern:pat )|+ $( if $guard: expr )? => $ret:expr) => {
        match $expression {
            $( $pattern )|+ $( if $guard )? => Some($ret),
            _ => None
        }
    }
}

enum E { A(i32), B(i32), C }

fn main() {
    assert_eq!(match_map!(E::A(21), E::A(x) | E::B(x) => x * 2), Some(42));
}

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=a3df83eee3d96f05233d78d00f7bc801

I use this macro as often as matches and think it would be useful to have it in std.


Compared to using the more verbose match/if-let way, this macro also has the benefit of readability, when the value is post-processed using .map(..)/.and_then(..)/.ok_or_else(..) or similar Option methods.

Compare:

    (match param {
        TrackParam::Ext(name) => Some(name),
        _ => None,
    }).and_then(|name| func(name))

and:

    match_map!(param, TrackParam::Ext(name) => name).and_then(|name| func(name))

Most helpful comment

I鈥檓 generally in favor. I tend to create similar macros for tests.

I also create derive macros (Decompose) to produce this as an inherent method.

All 4 comments

Maybe put this in a crate for a while and see if it gets picked up.

I鈥檓 generally in favor. I tend to create similar macros for tests.

I also create derive macros (Decompose) to produce this as an inherent method.

We might pursue the field puns color with a proc macro that extracts variant's fields based upon names?

enum Foo {
    A(..),  // Ignore all tuple variants because .0, etc. rarely turn out consistent enough
    B { x: u32, y: u32 },
    C { x: u16, y: u32 },
}

fn foo(f: Foo) {
    variant_field! { f; f.x }  // Error, x has inconsistent types
    variant_field! { f; u32::from(f.x) }  // Ok, the code inside resolves in all cases
    variant_field! { f; f.y }  // Ok, all y have consistent types
}

An easier syntax might be

    variant_field! { f { x, ..}; bar(x) }  // Error, x has inconsistent types

It'd desugar as applying this binding {x, ..} to all variants with those fields and returning Some(..), or _ => None.

Another take on this: bikeshed!(AAA => BBB) => if AAA { Some(BBB) } else { None}. Then you'd have let opt1 = bikeshed!( let Foo::B { x, .. } = foo => x ) but also other things like let opt2 = bikeshed!(x > 3 => 4);.

Of course, I've also pondered before changing the implicit else { () } to else { SomeTrait::method() } so that this would work with just normal if. Shouldn't even cause inference problems, since you need to be producing a () from the if anyway today...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

silversolver1 picture silversolver1  路  3Comments

p-avital picture p-avital  路  3Comments

rust-highfive picture rust-highfive  路  4Comments

rudolfschmidt picture rudolfschmidt  路  3Comments

clarfonthey picture clarfonthey  路  3Comments