Checking for an enum variant

Hi,

I am looking for a way to see if a variable is of a specific variant of an enum, without specifying all variants in the type. Something like the following (this does not work)

enum Sample {
    Var1 {data: i32},
    Var2 {data1: i32, data2: i32},
    Var3 {data1: f32, data2:f32},
    Var4,
}

fn is_of_var(val: Sample, variant: ???) -> bool {
    match val {
        variant => true,
        _ => false
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test1() {
        let s = Sample::Var2{data1: 1, data2: 2};

        let is_var2 = is_of_var(s, Sample::Var2);
        let is_var1 = is_of_var(s, Sample::Var1);

        assert!(is_var2);
        assert!(!is_var1);
    }
}

Preferably, I can write is_of_var as a macro that will work with any enum. Any ideas on how to accomplish this task? I just need to know if the variable is of that enum variant, not what its values are

Thanks..

You can use matches!, but it does need to know the structure of the variant (tuple, struct, nothing):

        let is_var2 = matches!(s, Sample::Var2 { .. });
        let is_var1 = matches!(s, Sample::Var1 { .. });

Or you could use std::mem::discriminant(...). You would need to set things up with some sort of sample of each variant though, as explicit discriminants for arbitrary enums is not yet stabilized.

Alternatively, you could have a C-like enumeration which shadows your data-holding enumeration (ala this strum crate macro) and compare discriminant(s.into()) and discriminant(CLikeEnum::Name).

1 Like

In addition to @quinedot's suggestions, there is if let:

if let Sample::Var2 { .. } = s {
    // ...
} else {
    // ...
}

Not sure how this solves the problem. It requires writing a separate function for each variant of the enum:

fn is_var1() -> bool {
  if let Sample::var1(_) = s {
    true
  else
    false
}
fn is_var2() -> {
  if let Sample::Var2(_,__) = s {
    ...
}

Am I missing something?

Ahh, sorry, I didn't fully understand what you were looking for. My thinking was that instead of using functions/methods like is_var1, is_var2, etc. (or some kind of parametric is_of_var) you could replace if with if let (or while with while let) at the call site, so there's no function involved at all. But if you specifically need this as a function or macro then the approaches in @quinedot's answer will be of more help.

You don't actually -- { .. } will work for all of them:

pub enum Foo {
    Tuple(i32),
    Struct { x: i32 },
    Nothing,
}

pub fn foo(x: Foo) {
    match x {
        Foo::Tuple { .. } => {},
        Foo::Struct { .. } => {},
        Foo::Nothing { .. } => {},
    }    
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b509bb7ac03e91b35c554c6de7af6ce

For why it works, see https://rust-lang.github.io/rfcs/1506-adt-kinds.html

5 Likes

Oh, nice! I think that makes matches!(s, Sample::Var1 { .. }) the best fit.

Thanks for the clarification @cole-miller.

Thanks for all the quick and helpful answers. Here is my (currently) final solution:

macro_rules! is_of_var {
    ($val:ident, $var:path) => {
        match $val {
            $var{..} => true,
            _ => false
        }
    }
}

And it can be used as:

        let s = Sample::Var2{data1: 1, data2: 2};

        let is_var2 = is_of_var!(s, Sample::Var2); //true
        let is_var1 = is_of_var!(s, Sample::Var1); //false