我有这个枚举类型:
enum Animal { Dog(i32), Cat(u8), }
现在我有了一个以这个类型为参数的函数。我 * 知道 *(出于某种原因)输入总是Cat。我想实现这一点:
Cat
fn count_legs_of_cat(animal: Animal) -> u8 { if let Animal::Cat(c) = animal { c } else { unreachable!() } }
我能写得更短些和/或更习惯些吗?
de90aj5v1#
我所看到的是为每个枚举变体引入一个新的struct,然后在枚举上引入方法来分解它:
struct
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 }
你不需要这个结构体,因为你可以只返回u8,但是这可能很难跟踪,如果你有多个变量具有相同的内部类型,那么它可能是模糊的。多年来,已经有许多RFC为此提供语言支持(最近的一个是RFC 2593 — Enum variant types),该提议允许枚举变量(如Animal::Cat)也是独立类型,因此您的方法可以直接接受Animal::Cat。我几乎总是喜欢在我固有的实现中编写绝对正确的代码,并迫使调用者恐慌:
u8
Animal::Cat
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 } } }
我可能会使用match:
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, } } }
从Rust 1.34开始,除了固有的实现之外,我还会使用TryFrom trait:
TryFrom
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), } } }
考虑使用实现std::error::Error的专用错误类型,而不是在失败情况下直接返回Animal。您可能还希望实现From,以便从Cat/Dog返回到Animal。这一切都很乏味,所以宏可以很好地使用。我相信有很多很好的板条箱可以做到这一点,但我经常编写自己的一次性解决方案:
std::error::Error
Animal
From
Dog
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), } }
1cklez4t2#
我发现一个单一的宏是解决这个问题的最好方法(在最近的 rust )。
macro_rules! cast { ($target: expr, $pat: path) => { { if let $pat(a) = $target { // #1 a } else { panic!( "mismatch variant when cast to {}", stringify!($pat)); // #2 } } }; }
let cat = cast!(animal, Animal::Cat);
into_variant
self
&self
&mut self
{into,as,as_mut}_{variant}
Some(Animal(cat))
lymnna713#
试试enum-as-inner板条箱,它的工作原理和Shepmaster的答案完全一样。
e4eetjau4#
我写了一个小宏来提取已知的枚举变体:
#[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);
不过,我不确定这是否符合您的需要。
4条答案
按热度按时间de90aj5v1#
我所看到的是为每个枚举变体引入一个新的
struct
,然后在枚举上引入方法来分解它:你不需要这个结构体,因为你可以只返回
u8
,但是这可能很难跟踪,如果你有多个变量具有相同的内部类型,那么它可能是模糊的。多年来,已经有许多RFC为此提供语言支持(最近的一个是RFC 2593 — Enum variant types),该提议允许枚举变量(如
Animal::Cat
)也是独立类型,因此您的方法可以直接接受Animal::Cat
。我几乎总是喜欢在我固有的实现中编写绝对正确的代码,并迫使调用者恐慌:
我可能会使用
match
:从Rust 1.34开始,除了固有的实现之外,我还会使用
TryFrom
trait:考虑使用实现
std::error::Error
的专用错误类型,而不是在失败情况下直接返回Animal
。您可能还希望实现From
,以便从Cat
/Dog
返回到Animal
。这一切都很乏味,所以宏可以很好地使用。我相信有很多很好的板条箱可以做到这一点,但我经常编写自己的一次性解决方案:
1cklez4t2#
我发现一个单一的宏是解决这个问题的最好方法(在最近的 rust )。
into_variant
和friends相反,这个宏覆盖了所有权使用,如self
,&self
和&mut self
。另一方面,{into,as,as_mut}_{variant}
解决方案通常需要3 * N个方法定义,其中N是变量的数量。Some(Animal(cat))
这样的嵌套模式,但对于普通使用来说已经足够好了。lymnna713#
试试enum-as-inner板条箱,它的工作原理和Shepmaster的答案完全一样。
e4eetjau4#
我写了一个小宏来提取已知的枚举变体:
不过,我不确定这是否符合您的需要。