【问题标题】:Unwrap inner type when enum variant is known当已知枚举变体时展​​开内部类型
【发布时间】:2016-04-29 11:12:14
【问题描述】:

我有这个枚举类型:

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

现在我有一个将这种类型作为参数的函数。我知道(出于某种原因)输入始终是Cat。我想实现这个:

fn count_legs_of_cat(animal: Animal) -> u8 {
    if let Animal::Cat(c) = animal { c } else { unreachable!() }
}

我可以写这个更短和/或更惯用的吗?

【问题讨论】:

  • 截至 2019 年,此代码实际上确实适用于稳定版 - 自从提出此问题以来,枚举得到了改进。见:play.rust-lang.org/…>
  • @alias65536 该代码在当时也有效。问题在于是否存在更好的方法。

标签: enums rust


【解决方案1】:

不是真的。我所看到的是为每个枚举变体引入一个新的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,但这可能很难跟踪。

不过,未来会有一丝更好的支持。我认为"efficient code reuse" RFC,但在博客文章Virtual Structs Part 3: Bringing Enums and Structs Together 中有更好的描述。建议是允许 Animal::Cat 成为独立类型,因此您的方法可以接受 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

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

【讨论】:

  • 我倾向于使用 Into 特征而不是 impl Animal,例如 this playground example。使用简单的枚举,使用宏来生成 impl 应该相对容易,但这不是我经常使用的模式,所以我从来没有真正开始编写它......
【解决方案2】:

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

【讨论】:

  • 也许您可以添加一个示例来说明如何使用板条箱?
【解决方案3】:

我写了一个小宏来提取已知的枚举变量:

#[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】:

    我发现一个宏是解决问题的最佳方法(在最近的 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);
    

    说明:

    • #1 if let 利用了最新的 Rust 编译器的智能模式匹配。与into_variant 和朋友等其他解决方案相反,这一宏涵盖了所有所有权使用,如self&amp;self&amp;mut self。另一方面{into,as,as_mut}_{variant} 解决方案通常需要 3 * N 方法定义,其中 N 是变体的数量。

    • #2 如果变体和值不匹配,宏将简单地恐慌并报告预期的模式。

    • 然而,宏不处理像Some(Animal(cat)) 这样的嵌套模式。但是对于普通使用来说已经足够了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-08-09
      • 1970-01-01
      • 2023-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-19
      相关资源
      最近更新 更多