Reputation: 88556
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
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
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
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
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
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
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