1

Given an enum like

struct Earth { water: usize }
struct Mars { redness: usize }

enum World {
    Mars(Mars),
    Earth(Earth),
}

A common pattern I write is

fn something_expecting_mars(planet: World) {
    let mars = match planet {
        World::Mars(data) => data,
        _ => panic!("Shouldn't be here now"),
    }
}

Is there a macro I can use to expect a variant of an enum and subsequently extract its data?

// rewriting to this
let mars = expect_v!(planet, World::Mars);
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user82395214
  • 829
  • 14
  • 37

2 Answers2

3

The standard library provides a macro for testing a match, but not one for extracting a value. However, it's fairly easy to write one:

macro_rules! expect_v {
    ($e:expr, $p:path) => {
        match $e {
            $p(value) => value,
            _ => panic!("expected {}", stringify!($p)),
        }
    };
}

Playground

As suggested in answers to the related question brought up in the comments, you might want to decouple value extraction from the panic. In that case, return an Option instead and let the callers panic if they wish by calling unwrap():

macro_rules! extract {
    ($e:expr, $p:path) => {
        match $e {
            $p(value) => Some(value),
            _ => None,
        }
    };
}

// ...
fn something_expecting_mars(planet: World) {
    let mars = extract!(planet, World::Mars).unwrap();
}
user4815162342
  • 141,790
  • 18
  • 296
  • 355
  • This is exactly what I was looking for, thanks! In the `panic`, is it possible to print the expected vs actual path name? – user82395214 May 24 '21 at 17:45
  • @user82395214 Printing what was expected is fairly easy using `stringify!()`, I've now amended the answer to use it. I don't think it's possible to print the actual variant, at least not with a declarative macro. But if you're ok with requiring `Debug`, you could print the whole value, i.e. change the second match arm to `other => panic!("expected {}, got {:?}", stringify!($p), other)`. – user4815162342 May 24 '21 at 18:24
0

Anything wrong with just using if let instead of match?

mars = if let World::Mars(data) = planet { data } else { panic!("Woot woot")}
cadolphs
  • 9,014
  • 1
  • 24
  • 41
  • 1
    This should probably be posted a comment, not an answer, since the OP explicitly asked about a macro and even provided the expected usage. Also note that `rustfmt` will reformat this into [four lines](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c536ca44e299a004f16d98b289793315) so it's not particularly elegant. – user4815162342 May 24 '21 at 15:44