Lukas Kalbertodt
Lukas Kalbertodt

Reputation: 88556

Unwrap inner type when enum variant is known

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?

Upvotes: 94

Views: 46671

Answers (6)

Jasha
Jasha

Reputation: 7629

The derive_more crate exports a TryInto derive macro that generates implementations of the standard library's TryFrom trait. The trait enables conversion from the given enum type to the inner type wrapped by a given variant.

Here is a usage example from the derive_more::TryInto docs:

use core::convert::TryFrom;
use core::convert::TryInto;
#[derive(TryInto, Clone)]
#[try_into(owned, ref, ref_mut)]
enum MixedData {
    Int(u32),
    String(String),
}

fn main() {
    let string = MixedData::String("foo".to_string());
    let int = MixedData::Int(123);
    assert_eq!(Ok(123u32), int.clone().try_into());
    assert_eq!(Ok(&123u32), (&int.clone()).try_into());
    assert_eq!(Ok(&mut 123u32), (&mut int.clone()).try_into());
    assert_eq!("foo".to_string(), String::try_from(string.clone()).unwrap());
    assert!(u32::try_from(string).is_err());
}

This approach could be combined with the enum-as-inner crate mentioned in @Black Marco's answer above which generates accessor methods with names corresponding to the enum variants.

Upvotes: 1

Black Marco
Black Marco

Reputation: 651

Try the enum-as-inner crate. It generates accessor methods as suggested in Shepmaster's answer.

Here is example usage loosely based on the crate's README:

use enum_as_inner::EnumAsInner;

#[derive(Debug, EnumAsInner)]
enum MyEnum {
    Zero,
    One(u32),
    Two(u32, i32),
    Three { a: bool, b: u32, c: i64 },
}

fn main() {
    let zero = MyEnum::Zero;
    assert!(zero.is_zero());

    let one = MyEnum::One(1);
    assert_eq!(one.into_one().unwrap(), 1);

    let mut two = MyEnum::Two(1, 2);
    *two.as_two_mut().unwrap().0 = 42;  // Set the first field to 42

    let three = MyEnum::Three { a: true, b: 1, c: 2 };
    assert_eq!(three.into_three().unwrap(), (true, 1, 2));
}

As of v0.6.0 of the crate, the methods generated include:

  • fn is_FIELDNAME(&self) -> bool
  • fn as_FIELDNAME(&self) -> Option<&T>
  • fn as_FIELDNAME_mut(&mut self) -> Option<&mut T>
  • fn into_FIELDNAME(self) -> Result<T, Self> where T is the inner type corresponding to the named field.

Upvotes: 20

Chayim Friedman
Chayim Friedman

Reputation: 70890

This is not shorter with this simple method, but if you have a lot of processing to do on the data, you can use let-else, stabilized in Rust 1.65.0:

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

Upvotes: 8

Shepmaster
Shepmaster

Reputation: 430524

What I have seen is to introduce 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
}

You don't need the struct as you could just return the u8, but that may get hard to keep track of. If you have multiple variants with the same inner type, then it would potentially be ambigous.

Over the years, there have been a number of RFCs to provide language support for this (a recent one being RFC 2593 — Enum variant types). The proposal would allow enum variants like Animal::Cat to also be standalone types, thus your method could accept an Animal::Cat directly.


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
        }
    }
}

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,
        }
    }
}

Since Rust 1.34, I'd use the TryFrom trait in addition to or instead of the inherent implementations:

impl TryFrom<Animal> for Cat {
    type Error = Animal;
    
    fn try_from(other: Animal) -> Result<Self, Self::Error> {
        match other {
            Animal::Cat(c) => Ok(c),
            a => Err(a),
        }
    }
}

impl TryFrom<Animal> for Dog {
    type Error = Animal;
    
    fn try_from(other: Animal) -> Result<Self, Self::Error> {
        match other {
            Animal::Dog(d) => Ok(d),
            a => Err(a),
        }
    }
}

Consider using a dedicated error type that implements std::error::Error instead of directly returning the Animal in the failure case. You may also want to implement From to go from Cat / Dog back to Animal.

This can all get tedious, so a macro can be of good use. I'm sure there are plenty of good crates out that that do this, but I often write my own one-off solution:

macro_rules! enum_thing {
    (
        enum $Name:ident {
            $($Variant:ident($f:ident)),* $(,)?
        }
    ) => {
        enum $Name {
            $($Variant($Variant),)*
        }

        $(
            struct $Variant($f);

            impl TryFrom<$Name> for $Variant {
                type Error = $Name;

                fn try_from(other: $Name) -> Result<Self, Self::Error> {
                    match other {
                        $Name::$Variant(v) => Ok(v),
                        o => Err(o),
                    }
                }
            }
        )*
    };
}

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

Upvotes: 46

Herrington Darkholme
Herrington Darkholme

Reputation: 6315

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.

Upvotes: 18

xiGUAwanOU
xiGUAwanOU

Reputation: 334

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.

Upvotes: 3

Related Questions