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