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

标签 enums rust

我有这个枚举类型:

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

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

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

我可以写得更短和/或更地道吗?

最佳答案

我看到的是为每个枚举变体引入一个新的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


我几乎总是喜欢在我固有的实现中编写绝对可靠的代码并迫使调用者 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
        }
    }
}

我可能会使用匹配:

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除了或代替固有实现的特性:

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

关于enums - 当枚举变体已知时解包内部类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34953711/

相关文章:

javascript - 如何在 ReactJs typescript 中将多个枚举值传递给变量

perl - 如何将枚举类型验证为 Perl 子例程参数?

rust - Actix Web actor的创建不止一次

rust - 如何检查 NEAR 账户是否部署了智能合约并实现了所需的接口(interface)?

rust - 为什么允许在一个表达式中同时具有数值类型向量的不可变和可变借用?

error-handling - Rust——如何精确优雅地处理错误?

php - 拉拉维尔 9 : how to use enums in blade (<select>)?

C enum 打印错误?

c# - WPF 将枚举绑定(bind)到组合框

rust - 为什么我们不需要从某些 Rust 迭代器中的 Result 中提取值?