Как мне сопоставить значения перечисления с целым числом?

Я могу получить целочисленное значение перечислений, как это:

enum MyEnum {
    A = 1,
    B,
    C,
}

let x = MyEnum::C as i32;

но я не могу этого сделать:

match x {
    MyEnum::A => {}
    MyEnum::B => {}
    MyEnum::C => {}
    _ => {}
}

Как можно сопоставить значения перечисления или попытаться преобразовать x вернуться к MyEnum?

Я вижу, что такая функция полезна для перечислений, но, вероятно, ее не существует:

impl MyEnum {
    fn from<T>(val: &T) -> Option<MyEnum>;
}

10 ответов

Since Rust 1.34, I recommend implementing TryFrom:

use std::convert::TryFrom;

impl TryFrom<i32> for MyEnum {
    type Error = ();

    fn try_from(v: i32) -> Result<Self, Self::Error> {
        match v {
            x if x == MyEnum::A as i32 => Ok(MyEnum::A),
            x if x == MyEnum::B as i32 => Ok(MyEnum::B),
            x if x == MyEnum::C as i32 => Ok(MyEnum::C),
            _ => Err(()),
        }
    }
}

Then you can use TryInto and handle the possible error:

use std::convert::TryInto;

fn main() {
    let x = MyEnum::C as i32;

    match x.try_into() {
        Ok(MyEnum::A) => println!("a"),
        Ok(MyEnum::B) => println!("b"),
        Ok(MyEnum::C) => println!("c"),
        Err(_) => eprintln!("unknown number"),
    }
}

See also:

Вы можете получить FromPrimitive:

#[macro_use]
extern crate num_derive;
extern crate num_traits;

use num_traits::FromPrimitive;

#[derive(FromPrimitive)]
enum MyEnum {
    A = 1,
    B,
    C,
}

fn main() {
    let x = 2;

    match FromPrimitive::from_i32(x) {
        Some(MyEnum::A) => println!("Got A"),
        Some(MyEnum::B) => println!("Got B"),
        Some(MyEnum::C) => println!("Got C"),
        None            => println!("Couldn't convert {}", x),
    }
}

Вы можете воспользоваться охраной матчей, чтобы написать эквивалентную, но более грубую конструкцию:

match x {
    x if x == MyEnum::A as i32 => ...,
    x if x == MyEnum::B as i32 => ...,
    x if x == MyEnum::C as i32 => ...,
    _ => ...
}

std::mem::transmute также можно использовать:

let y: MyEnum = unsafe { transmute(x as i8) };

Но для этого необходимо, чтобы вы знали размер перечисления, поэтому вы можете сначала привести к соответствующему скаляру, а также, если x не является допустимым значением для перечисления.

Если вы уверены, что значения целого числа включены в перечисление, вы можете использовать std::mem::transmute,

Это должно быть использовано с #[repr(..)] контролировать базовый тип.

Полный пример:

#[repr(i32)]
enum MyEnum {
    A = 1, B, C
}

fn main() {
    let x = MyEnum::C;
    let y = x as i32;
    let z: MyEnum = unsafe { ::std::mem::transmute(y) };

    // match the enum that came from an int
    match z {
        MyEnum::A => { println!("Found A"); }
        MyEnum::B => { println!("Found B"); }
        MyEnum::C => { println!("Found C"); }
    }
}

Обратите внимание, что в отличие от некоторых других ответов, для этого требуется только стандартная библиотека Rust.

std::num::FromPrimitive помечен как нестабильный и не будет включен в Rust 1.0. В качестве обходного пути я написал enum_primitive ящик, который экспортирует макрос enum_from_primitive! это оборачивает enum декларация и автоматически добавляет реализацию num::FromPrimitive (от num ящик). Пример:

#[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);
}

Если целое число, с которым вы сопоставляете, основано на порядке вариантов перечисления, вы можете использовать strum для генерации итератора перечислений и выбрать правильный:

#[macro_use]
extern crate strum_macros; // 0.9.0
extern crate strum;        // 0.9.0

use strum::IntoEnumIterator;

#[derive(Debug, PartialEq, EnumIter)]
enum MyEnum {
    A = 1,
    B,
    C,
}

fn main() {
    let e = MyEnum::iter().nth(2);
    assert_eq!(e, Some(MyEnum::C));
}

В настоящее время я использую этот фрагмент кода для преобразования целых чисел в перечисление:

      #[rustfmt::skip]
#[derive(Debug, Clone, Copy)]
pub enum Square {
    A8, B8, C8, D8, E8, F8, G8, H8,
    A7, B7, C7, D7, E7, F7, G7, H7,
    A6, B6, C6, D6, E6, F6, G6, H6,
    A5, B5, C5, D5, E5, F5, G5, H5,
    A4, B4, C4, D4, E4, F4, G4, H4,
    A3, B3, C3, D3, E3, F3, G3, H3,
    A2, B2, C2, D2, E2, F2, G2, H2,
    A1, B1, C1, D1, E1, F1, G1, H1,
}

impl TryFrom<u64> for Square {
    type Error = String;

    fn try_from(value: u64) -> Result<Self, Self::Error> {
        use Square::*;

        #[rustfmt::skip]
        const LOOKUP: [Square; 64] = [
            A8, B8, C8, D8, E8, F8, G8, H8,
            A7, B7, C7, D7, E7, F7, G7, H7,
            A6, B6, C6, D6, E6, F6, G6, H6,
            A5, B5, C5, D5, E5, F5, G5, H5,
            A4, B4, C4, D4, E4, F4, G4, H4,
            A3, B3, C3, D3, E3, F3, G3, H3,
            A2, B2, C2, D2, E2, F2, G2, H2,
            A1, B1, C1, D1, E1, F1, G1, H1,
        ];

        LOOKUP
            .get(value as usize)
            .ok_or_else(|| {
                format!(
                    "index '{}' is not valid, make sure it's in the range '0..64'",
                    value
                )
            })
            .map(|s| *s)
    }
}

Я не знаю, насколько это быстро, но я, вероятно, узнаю в будущем.

Конечно, недостатком этого является то, что если перечисление будет изменено, его придется обновлять в нескольких местах.

Прочитав вопрос еще пару раз, это не совсем то, о чем просили. Но я пока оставлю этот ответ здесь, так как эта ветка появляется при поиске «rust convert integer to enum». Однако ответ все еще может быть использован для решения проблемы. Просто преобразуйте x в перечисление ( (на самом деле вы не должны просто разворачивать, а обрабатывать ошибку)), а затем использовать обычный оператор сопоставления с вариантами перечисления.

В идеале вы могли бы привести значения перечисления к целым числам:

      match x {
    MyEnum::A as i32 => {}
    MyEnum::B as i32 => {}
    MyEnum::C as i32 => {}
    _ => {}
}

Однако это не компилируется. Вы не можете использовать выражения (например,as) в узорах.

Однако вы можете использоватьconstsв шаблонах и константах можно использовать выражения:

      const A: i32 = MyEnum::A as i32;
const B: i32 = MyEnum::B as i32;
const C: i32 = MyEnum::C as i32;
match x {
    A => {}
    B => {}
    C => {}
    _ => {}
}

Нестабильная встроенная константа может сделать это намного лучше:

      #![feature(inline_const_pat)]

match x {
    const { MyEnum::A as i32 } => {}
    const { MyEnum::B as i32 } => {}
    const { MyEnum::C as i32 } => {}
    _ => {}
}

Я написал простой макрос, который преобразует числовое значение обратно в перечисление:

macro_rules! num_to_enum {
    ($num:expr => $enm:ident<$tpe:ty>{ $($fld:ident),+ }; $err:expr) => ({
        match $num {
            $(_ if $num == $enm::$fld as $tpe => { $enm::$fld })+
            _ => $err
        }
    });
}

Вы можете использовать это так:

#[repr(u8)] #[derive(Debug, PartialEq)]
enum MyEnum {
    Value1 = 1,
    Value2 = 2
}

fn main() {
    let num = 1u8;
    let enm: MyEnum = num_to_enum!(
        num => MyEnum<u8>{ Value1, Value2 };
        panic!("Cannot convert number to `MyEnum`")
    );
    println!("`enm`: {:?}", enm);
}

Вы можете использовать Strum для созданияfrom_reprреализация с использованиемFromReprполучить макрос:

      use strum::FromRepr;

#[derive(FromRepr, Debug, PartialEq)]
#[repr(u8)]
enum MyEnum {
    A = 1,
    B,
    C,
}

fn main() {
    let e = MyEnum::from_repr(2);
    assert_eq!(e, Some(MyEnum::B));
}

Груз.томл

      [dependencies]
strum = { version = "0.25", features = ["derive"] }
Другие вопросы по тегам