patternrustModerate
Unwrap inner type when enum variant is known
Viewed 0 times
enumknownunwrapvariantinnerwhentype
Problem
I have this enum type:
Now I have a function that takes this type as parameter. I know (for some reason) that the input is always a
Can I write this shorter and/or more idiomatic?
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?
Solution
What I have seen is to introduce a new
You don't need the struct as you could just return the
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
I almost always prefer to write the infallible code in my inherent implementation and force the caller to panic:
I'd probably use a
Since Rust 1.34, I'd use the
Consider using a dedicated error type that implements
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:
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 {
if let Animal::Cat(c) = self {
Some(c)
} else {
None
}
}
fn dog(self) -> Option {
if let Animal::Dog(d) = self {
Some(d)
} else {
None
}
}
}
I'd probably use a
match:impl Animal {
fn cat(self) -> Option {
match self {
Animal::Cat(c) => Some(c),
_ => None,
}
}
fn dog(self) -> Option {
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 for Cat {
type Error = Animal;
fn try_from(other: Animal) -> Result {
match other {
Animal::Cat(c) => Ok(c),
a => Err(a),
}
}
}
impl TryFrom for Dog {
type Error = Animal;
fn try_from(other: Animal) -> Result {
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 for $Variant {
type Error = $Name;
fn try_from(other: $Name) -> Result {
match other {
$Name::$Variant(v) => Ok(v),
o => Err(o),
}
}
}
)*
};
}
enum_thing! {
enum Animal {
Dog(i32),
Cat(u8),
}
}
Context
Stack Overflow Q#34953711, score: 47
Revisions (0)
No revisions yet.