Как сопоставить значения enum с целым числом?
Я могу получить целочисленное значение перечислений, как это:
enum MyEnum {
A = 1,
B,
C,
}
let x = MyEnum::C as i32;
но я не могу это сделать:
match x {
MyEnum::A => {}
MyEnum::B => {}
MyEnum::C => {}
_ => {}
}
Как я могу либо сопоставить значения enum, либо попытаться преобразовать x
обратно в MyEnum
?
Я вижу, что такая функция полезна для перечислений, но она, вероятно, не существует:
impl MyEnum {
fn from<T>(val: &T) -> Option<MyEnum>;
}
Ответы
Ответ 1
Вы можете получить FromPrimitive
. Использование Rust 2018 упрощенного синтаксиса импорта:
use num_derive::FromPrimitive;
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),
}
}
В вашем Cargo.toml
:
[dependencies]
num-traits = "0.2"
num-derive = "0.2"
Больше подробностей в ящике с номерами, смотрите esp. Образец использует в тестах.
Ответ 2
Вы можете использовать защитные устройства для записи эквивалента, но clunkier, конструкция:
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) };
Но для этого требуется, чтобы вы знали размер перечисления, поэтому сначала можете применить его к соответствующему скаляру, а также создать поведение undefined, если x
не является допустимым значением для перечисления.
Ответ 3
std::num::FromPrimitive
помечен как неустойчивый и не будет включен в Rust 1.0. В качестве обходного пути я написал enum_primitive
crate, который экспортирует макрос 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);
}
Ответ 4
Если вы уверены, что значения целого числа включены в перечисление, вы можете использовать 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.
Ответ 5
Я написал простой макрос, который преобразует числовое значение обратно в перечисление:
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);
}
Ответ 6
Начиная с Rust 1.34, я рекомендую реализовать 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(()),
}
}
}
Затем вы можете использовать TryInto
и обработать возможную ошибку:
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"),
}
}
Ответ 7
Если целое число, с которым вы сравниваете, основано на порядке вариантов перечисления, вы можете использовать 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));
}