当enum变量已知时展开内部类型

Luk*_*odt 12 enums rust

我有这个枚举类型:

enum Animal {
    Dog(i32),
    Cat(u8),
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

小智 16

尝试使用枚举作为内部板条箱。它按照Shepmaster 的回答中的建议生成访问器方法。

以下是基于板条箱自述文件的松散用法示例:

use enum_as_inner::EnumAsInner;

#[derive(Debug, EnumAsInner)]
enum MyEnum {
    Zero,
    One(u32),
    Two(u32, i32),
    Three { a: bool, b: u32, c: i64 },
}

fn main() {
    let zero = MyEnum::Zero;
    assert!(zero.is_zero());

    let one = MyEnum::One(1);
    assert_eq!(one.into_one().unwrap(), 1);

    let mut two = MyEnum::Two(1, 2);
    *two.as_two_mut().unwrap().0 = 42;  // Set the first field to 42

    let three = MyEnum::Three { a: true, b: 1, c: 2 };
    assert_eq!(three.into_three().unwrap(), (true, 1, 2));
}
Run Code Online (Sandbox Code Playgroud)

从 crate v0.6.0 开始,生成的方法包括:

  • fn is_FIELDNAME(&self) -> bool
  • fn as_FIELDNAME(&self) -> Option<&T>
  • fn as_FIELDNAME_mut(&mut self) -> Option<&mut T>
  • fn into_FIELDNAME(self) -> Result<T, Self> 其中T是与命名字段对应的内部类型。

  • 也许您可以添加一个示例来展示如何使用该板条箱? (14认同)

Her*_*lme 15

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

Run Code Online (Sandbox Code Playgroud)

宏的使用


let cat = cast!(animal, Animal::Cat);
Run Code Online (Sandbox Code Playgroud)

解释:

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

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

  • 然而,该宏不处理像Some(Animal(cat)). 但对于一般用途来说已经足够了。


She*_*ter 14

并不是的.我所看到的是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
}
Run Code Online (Sandbox Code Playgroud)

当然,你不需要结构,你可以只返回u8,但这可能很难跟踪.

然而,未来会有更好的支持.我认为这是"高效的代码重用"RFC,但更好地描述在博客文章虚拟结构第3部分:将枚举和结构结合在一起.该提议将允许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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我可能会用一个 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,
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 而不是impl动物,我倾向于推动进入特质,就像[这个游乐场的例子](http://is.gd/jmS3RB).使用简单的枚举,使用宏来生成impl应该相对容易,但它不是我用得那么多的模式,所以我从来没有真正开始编写它... (4认同)