简体   繁体   English

当枚举变体已知时解包内部类型

[英]Unwrap inner type when enum variant is known

I have this enum type:我有这个枚举类型:

enum Animal {
    Dog(i32),
    Cat(u8),
}

Now I have a function that takes this type as parameter.现在我有一个 function 将此类型作为参数。 I know (for some reason) that the input is always a Cat .知道(出于某种原因)输入始终是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?我可以把这个写得更短和/或更地道吗?

Not really.并不真地。 What I have seen is introducing a new struct for each enum variant, and then methods on the enum to decompose it:我所看到的是为每个枚举变体引入一个新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
}

Of course, you don't need the struct and you could just return the u8 , but that may get hard to track.当然,您不需要结构体,您可以只返回u8 ,但这可能很难跟踪。

There's a glimmer of better support for this in the future, however.然而,未来会有更好的支持。 I think it's the "efficient code reuse" RFC , but better described in the blog post Virtual Structs Part 3: Bringing Enums and Structs Together .认为这是“高效的代码重用”RFC ,但在博客文章Virtual Structs Part 3:Bringing Enums and Structs Together 中有更好的描述。 The proposal would be to allow Animal::Cat to be a standalone type, thus your method could accept an Animal::Cat and not have to worry about it.提议是允许Animal::Cat成为独立类型,因此您的方法可以接受Animal::Cat而不必担心它。


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

And I'd probably use a 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,
        }
    }
}

I found one single macro is the best way to solve the problem (in recent Rust).我发现一个宏是解决问题的最佳方法(在最近的 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. #1 if let 利用了最近的 Rust 编译器的智能模式匹配。 Contrary to other solutions like into_variant and friends, this one macro covers all ownership usage like self , &self and &mut self .into_variant和 friends 等其他解决方案相反,这个宏涵盖了所有所有权用法,如self&self&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.另一方面{into,as,as_mut}_{variant}解决方案通常需要 3 * N 方法定义,其中 N 是变体的数量。

  • #2 If the variant and value mismatch, the macro will simply panic and report the expected pattern. #2 如果变体和值不匹配,宏将简单地恐慌并报告预期的模式。

  • The macro, however, does not handle nested pattern like Some(Animal(cat)) .但是,宏不处理像Some(Animal(cat))这样的嵌套模式。 But it is good enough for common usage.但对于普通使用来说已经足够好了。

试试enum-as-inner crate,它完全符合 Shepmaster 的回答。

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.但是,我不确定这是否适合您的需要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM