Unwrap inner type when enum variant is known

EnumsRust

Enums Problem Overview


I have this enum type:

enum Animal {
    Dog(i32),
    Cat(u8),
}

Now I have a function that takes this type as parameter. I know (for some reason) that the input is always a Cat. I want to achieve this:

fn count_legs_of_cat(animal: Animal) -> u8 {
    if let Animal::Cat(c) = animal { c } else { unreachable!() }
}

Can I write this shorter and/or more idiomatic?

Enums Solutions


Solution 1 - Enums

Not really. What I have seen is introducing a new struct for each enum variant, and then methods on the enum to decompose it:

struct Dog(i32);
struct Cat(u8);

enum Animal {
    Dog(Dog),
    Cat(Cat),
}

impl Animal {
    fn cat(self) -> Cat {
        if let Animal::Cat(c) = self { c } else { panic!("Not a cat") }
    }

    fn dog(self) -> Dog {
        if let Animal::Dog(d) = self { d } else { panic!("Not a dog") }
    }
}

// Or better an impl on `Cat` ?
fn count_legs_of_cat(c: Cat) -> u8 {
    c.0
}

Of course, you don't need the struct and you could just return the u8, but that may get hard to track.

There's a glimmer of better support for this in the future, however. I think it's the "efficient code reuse" RFC, but better described in the blog post Virtual Structs Part 3: Bringing Enums and Structs Together. The proposal would be to allow Animal::Cat to be a standalone type, thus your method could accept an Animal::Cat and not have to worry about it.


Personally, I almost always prefer to write the infallible code in my inherent implementation and force the caller to panic:

impl Animal {
    fn cat(self) -> Option<Cat> {
        if let Animal::Cat(c) = self {
            Some(c)
        } else {
            None
        }
    }

    fn dog(self) -> Option<Dog> {
        if let Animal::Dog(d) = self {
            Some(d)
        } else {
            None
        }
    }
}

And I'd probably use a match

impl Animal {
    fn cat(self) -> Option<Cat> {
        match self {
            Animal::Cat(c) => Some(c),
            _ => None,
        }
    }

    fn dog(self) -> Option<Dog> {
        match self {
            Animal::Dog(d) => Some(d),
            _ => None,
        }
    }
}

Solution 2 - Enums

Try enum-as-inner crate, it work exactly what Shepmaster's answer have done.

Solution 3 - Enums

I found one single macro is the best way to solve the problem (in recent Rust).

Macro Definition

    macro_rules! cast {
        ($target: expr, $pat: path) => {
            {
                if let $pat(a) = $target { // #1
                    a
                } else {
                    panic!(
                        "mismatch variant when cast to {}", 
                        stringify!($pat)); // #2
                }
            }
        };
    }

Macro Usage


let cat = cast!(animal, Animal::Cat);

Explanation:

  • #1 The if let exploits recent Rust compiler's smart pattern matching. Contrary to other solutions like into_variant and friends, this one macro covers all ownership usage like self, &self and &mut self. On the other hand {into,as,as_mut}_{variant} solution usually needs 3 * N method definitions where N is the number of variants.

  • #2 If the variant and value mismatch, the macro will simply panic and report the expected pattern.

  • The macro, however, does not handle nested pattern like Some(Animal(cat)). But it is good enough for common usage.

Solution 4 - Enums

I wrote a small macro for extracting the known enum variant:

#[macro_export]
macro_rules! extract_enum_value {
  ($value:expr, $pattern:pat => $extracted_value:expr) => {
    match $value {
      $pattern => $extracted_value,
      _ => panic!("Pattern doesn't match!"),
    }
  };
}

let cat = extract_enum_value!(animal, Animal::Cat(c) => c);

However, I'm not sure if this fits into your need.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLukas KalbertodtView Question on Stackoverflow
Solution 1 - EnumsShepmasterView Answer on Stackoverflow
Solution 2 - EnumsBlack MarcoView Answer on Stackoverflow
Solution 3 - EnumsHerrington DarkholmeView Answer on Stackoverflow
Solution 4 - EnumsxiGUAwanOUView Answer on Stackoverflow