Extending `matches!` to handle different return types

Currently the matches! macro can only return bool. What if, as a non-breaking change, another branch was added that accepted => expr. If the pattern is matched, then expr is returned, otherwise Default::default():

#[macro_export]
macro_rules! matches {
    ($expression:expr, $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
        matches!($expression, $( $pattern )|+ $( $guard )? => true)
    };
    ($expression:expr, $( $pattern:pat_param )|+ $( if $guard: expr )? => $then:expr $(,)?) => {
        match $expression {
            $( $pattern )|+ $( if $guard )? => $then,
            _ => Default::default()
        }
    }
}

One use case I had in mind was the as_variant pattern. Result::ok for example:

- pub fn ok(self) -> Option<T> {
-     match self {
-         Ok(x) => Some(x),
-         Err(_) => None,
-     }
- }

+ pub fn ok(self) -> Option<T> {
+     matches!(self, Ok(x) => Some(x))
+ }

Personally I find this to be an improvement in brevity at the cost of readability/understandability, which isn't a trade I'd personally make.

15 Likes

I personally find matches!(result, Ok(x) => Some(x)) very readable, but to each their own.

The thing that makes me less confident about this one is that by that point one is basically just saving the else { None }, at which point I'm more meh on it -- if let Ok(x) = result { Some(x) } else { None } isn't so bad that I think it's worth a macro.

Notably, the hidden Default::default() feels not quite right. Which things really would be used there? () would work, like matches!(result, Ok(x) => y = x), but at that point it's just if let in another order. For things like Strings it'd just perpetuate the "empty is special" mistakes that are so common in languages without Option, so I'm not a fan even though it wouldn't be a perf issue.

And -- though I'd probably not use it here -- we will soon have

pub fn ok(self) -> Option<T> {
    let Ok(x) = self else { return None };
    Some(x)
}

as another option here, so dunno if it's time to add more to matches!, especially since macro changes are insta-stable.

7 Likes

I think this won't be necessary as there are planned improvements that help similar use cases. You can look up fallible patterns in let and if-let chaining to match multiples things, there are a few RFCs but they all help with making matches! essentially not required in the future

Highly related, but I've considered for a while a macro (extract!) that has matches!-like syntax but appends => foo at the end. If it matches, it's Some, otherwise it's None. Note the lack of explicit Some in the bit that's appended.

So if you were to get the first element of an array (basically slice::first)

extract!(array, [el, ..] => el)
11 Likes

That was my original idea actually. To @scottmcm's point, I agree that enforcing None as the default branch is better than potentially things like "", and there's always the explicit unwrap_or_default if that is needed.

#[macro_export]
macro_rules! matches {
    ($expression:expr, $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => {
        matches!($expression, $( $pattern )|+ $( $guard )? => ()).is_some()
    };
    ($expression:expr, $( $pattern:pat_param )|+ $( if $guard: expr )? => $then:expr $(,)?) => {
        match $expression {
            $( $pattern )|+ $( if $guard )? => Some($then),
            _ => None
        }
    }
}
1 Like

I think having a separate macro like extract would be better, as matches sounds like it should always return a bool.

3 Likes

Personally I'd prefer such a macro to be called try_match! (which is also the name of my crate that is a precedent for this).

  • try_ implies the operation is fallible and evaluates to Option<_> or Result<_, _> in line with the standard convention.
  • Including match in the name clearly communicates that it involves pattern matching as in a match expression, and that the pattern matching is what is being fallible here.
1 Like

Adding this under a different name does seem attractive to me. I don’t see a strong case for having it be part of matches!(), especially since the return type is different.

2 Likes

For what it's worth I have just published an extract! macro under the extract_macro crate. It's imported with use extract::extract;. It accepts multiple arms since I didn't see a reason to restrict it.

Let's see how much the ecosystem takes this up.

4 Likes