簡體   English   中英

如何在 Rust 中創建具有常量值的枚舉?

[英]How can I create enums with constant values in Rust?

我可以做這個:

enum MyEnum {
    A(i32),
    B(i32),
}

但不是這個:

enum MyEnum {
    A(123), // 123 is a constant
    B(456), // 456 is a constant
}

我可以使用單個字段為AB創建結構,然后實現該字段,但我認為可能有更簡單的方法。 有沒有?

回答這個問題的最佳方法是弄清楚為什么要在枚舉中使用常量:您是將一個值與每個變體相關聯,還是希望每個變體成為該值(如 C 或 C++ 中的enum )?

對於第一種情況,只留下沒有數據的枚舉變體並創建一個函數可能更有意義:

enum MyEnum {
    A,
    B,
}

impl MyEnum {
    fn value(&self) -> i32 {
        match *self {
            MyEnum::A => 123,
            MyEnum::B => 456,
        }
    }
}
// call like some_myenum_value.value()

這種方法可以多次應用,將許多單獨的信息與每個變體相關聯,例如,也許你也想要一個.name() -> &'static str方法。 將來,這些函數甚至可以標記為const函數。

對於第二種情況,您可以指定顯式整數標記值,就像 C/C++ 一樣:

enum MyEnum {
    A = 123,
    B = 456,
}

這可以以所有相同的方式matched ,但也可以轉換為整數MyEnum::A as i32 (請注意,像MyEnum::A | MyEnum::B這樣的計算在 Rust 中並不是自動合法的:枚舉具有特定的值,它們不是位標志。)

創建具有常量值的“枚舉”,可以使用 structs 和關聯的常量來擴充。 這類似於像bitflags這樣的 crate 的工作方式以及它會生成什么。

此外,為了防止直接實例化MyEnum ,您可以使用#[non_exhaustive]對其進行標記。

#[non_exhaustive]
struct MyEnum;

impl MyEnum {
    pub const A: i32 = 123;
    pub const B: i32 = 456;
}

然后,您只需通過訪問MyEnum::AMyEnum::B就可以像其他方式一樣使用“枚舉”。

看到這個的人可能會FromPrimitive的引入和棄用。 一個可能在這里也有用的替代品是enum_primitive 它允許您使用類似 C 的枚舉並讓它們在數字和邏輯表示之間進行轉換:

#[macro_use]
extern crate enum_primitive;
extern crate num;

use num::FromPrimitive;

enum_from_primitive! {
    #[derive(Debug, PartialEq)]
    enum FooBar {
        Foo = 17,
        Bar = 42,
        Baz,
    }
}

fn main() {
    assert_eq!(FooBar::from_i32(17), Some(FooBar::Foo));
    assert_eq!(FooBar::from_i32(42), Some(FooBar::Bar));
    assert_eq!(FooBar::from_i32(43), Some(FooBar::Baz));
    assert_eq!(FooBar::from_i32(91), None);
}

enum-map crate 提供了為枚舉記錄分配值的能力。 更重要的是,您可以將此宏與不同的值類型一起使用。

use enum_map::{enum_map, Enum}; // 0.6.2

#[derive(Debug, Enum)]
enum Example {
    A,
    B,
    C,
}

fn main() {
    let mut map = enum_map! {
        Example::A => 1,
        Example::B => 2,
        Example::C => 3,
    };
    map[Example::C] = 4;

    assert_eq!(map[Example::A], 1);

    for (key, &value) in &map {
        println!("{:?} has {} as value.", key, value);
    }
}

這個怎么樣?

enum MyEnum {
    A = 123,
    B = 456,
}

assert_eq!(MyEnum::A as i32, 123i32);
assert_eq!(MyEnum::B as i32, 456i32);

只是提供另一個想法。

#[allow(non_snake_case, non_upper_case_globals)]
mod MyEnum {
    pub const A: i32 = 123;
    pub const B: i32 = 456;
}

然后,您可以通過訪問MyEnum::AMyEnum::Buse MyEnum::*來簡單地使用它。

這樣做相對於關聯常量的好處是您甚至可以嵌套更多枚舉。

#[allow(non_snake_case, non_upper_case_globals)]
mod MyEnum {
    pub const A: i32 = 123;
    pub const B: i32 = 456;

    #[allow(non_snake_case, non_upper_case_globals)]
    mod SubEnum {
        pub const C: i32 = 789;
    }
}

對於我的項目,我編寫了一個自動生成索引並設置初始值的宏。

#[macro_export]
macro_rules! cnum {
    (@step $_idx:expr,) => {};
    (@step $idx:expr, $head:ident, $($tail:ident,)*) => {
        pub const $head: usize = $idx;
        cnum!(@step $idx + 1usize, $($tail,)*);
    };
    ($name:ident; $($n:ident),* $(,)* $({ $($i:item)* })?) => {
        cnum!($name; 0usize; $($n),* $({ $($i)* })?);
    };
    ($name:ident; $start:expr; $($n:ident),* $(,)* $({ $($i:item)* })?) => {
        #[macro_use]
        #[allow(dead_code, non_snake_case, non_upper_case_globals)]
        pub mod $name {
            use crate::cnum;
            $($($i)*)?
            cnum!(@step $start, $($n,)*);
        }
    };
}

然后你可以像這樣使用它,

cnum! { Tokens;
    EOF,
    WhiteSpace,
    Identifier,
    {
        cnum! { Literal; 100;
            Numeric,
            String,
            True,
            False,
            Nil,
        }

        cnum! { Keyword; 200;
            For,
            If,
            Return,
        }
    }
}

我為此創建了一個 crate枚舉

使用我的箱子的例子:

use enumeration::prelude::*;

enumerate!(MyEnum(u8; i32)
    A = 123
    B = 456
);

pub fn main() {
    assert_eq!(*MyEnum::A.value(), 123);
    assert_eq!(*MyEnum::B.value(), 456);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM