82

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?

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • As of 2019, this code actually does work on stable - enums were improved since this question was asked. See: – jv-dev May 14 '19 at 14:05
  • 11
    @alias65536 The code worked back then, too. The question was rather about whether there exists a better way of doing that. – Lukas Kalbertodt May 14 '19 at 14:35

6 Answers6

40

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),
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 8
    instead of the impl Animal I tend to impl the Into trait, like [this playground example](http://is.gd/jmS3RB). With simple enums it should be relatively easy to have a macro to generate the impls, but it's not a pattern I use all that much, so I never actually got around to writing it... – Paolo Falabella Jan 23 '16 at 12:00
12

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.
Jasha
  • 5,507
  • 2
  • 33
  • 44
Black Marco
  • 541
  • 6
  • 5
12

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.

Herrington Darkholme
  • 5,979
  • 1
  • 27
  • 43
  • I like it - but would love it if it would work for struct-style enums too: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=645ebd48ece598ee0d7c2fa980a57d91 – TeNNoX Mar 01 '23 at 01:51
3

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.

xiGUAwanOU
  • 325
  • 2
  • 16
2

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
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
1

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.

Jasha
  • 5,507
  • 2
  • 33
  • 44