rust 枚举变量已知时展开内部类型

alen0pnh  于 2023-01-26  发布在  其他
关注(0)|答案(4)|浏览(160)

我有这个枚举类型:

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

现在我有了一个以这个类型为参数的函数。我 * 知道 *(出于某种原因)输入总是Cat。我想实现这一点:

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

我能写得更短些和/或更习惯些吗?

de90aj5v

de90aj5v1#

我所看到的是为每个枚举变体引入一个新的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
我几乎总是喜欢在我固有的实现中编写绝对正确的代码,并迫使调用者恐慌:

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

从Rust 1.34开始,除了固有的实现之外,我还会使用TryFrom trait:

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
这一切都很乏味,所以宏可以很好地使用。我相信有很多很好的板条箱可以做到这一点,但我经常编写自己的一次性解决方案:

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),
    }
}
1cklez4t

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);
    • 说明:**
  • #1 if let利用了最近Rust编译器的智能模式匹配。与其他解决方案如into_variant和friends相反,这个宏覆盖了所有权使用,如self&self&mut self。另一方面,{into,as,as_mut}_{variant}解决方案通常需要3 * N个方法定义,其中N是变量的数量。
  • #2如果变量和值不匹配,宏只会出现异常并报告预期的模式。
  • 然而,宏不能处理像Some(Animal(cat))这样的嵌套模式,但对于普通使用来说已经足够好了。
lymnna71

lymnna713#

试试enum-as-inner板条箱,它的工作原理和Shepmaster的答案完全一样。

e4eetjau

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);

不过,我不确定这是否符合您的需要。

相关问题