dhardy
dhardy

Reputation: 12144

How do I match enum values with an integer?

I can get an integer value of an enums like this:

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

let x = MyEnum::C as i32;

but I can't seem to do this:

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

How can I either match against the values of the enum or try to convert x back to a MyEnum?

I can see a function like this being useful for enums, but it probably doesn't exist:

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

Upvotes: 116

Views: 136655

Answers (10)

Chayim Friedman
Chayim Friedman

Reputation: 70830

Ideally, you would be able to cast the enum values to integers:

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

However this doesn't compile. You cannot use expressions (like as) in patterns.

However, you can use consts in patterns, and consts can use expressions:

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 => {}
    _ => {}
}

The unstable inline const can make this a lot nicer:

#![feature(inline_const_pat)]

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

Upvotes: 7

nazgul
nazgul

Reputation: 519

You can use strum to generate from_repr implementation using FromRepr derive macro:

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

Cargo.toml

[dependencies]
strum = { version = "0.25", features = ["derive"] }

Upvotes: 6

Elias
Elias

Reputation: 4112

I'm currently using this piece of code to convert from integers to an enum:

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

I don't know how quick this is, but I'm probably going to find out in the future.

Of course, a drawback of this is that if the enum gets changed, it will have to be updated in multiple places.

After reading the question a couple more times, this isn't really what was asked for. But I'll leave this answer here for now, as this thread shows up when searching for "rust convert integer to enum". The answer could still be used to solve the problem though. Simply convert x to the enum (x.try_into().unwrap() (you actually shouldn't really just unwrap, but handle the error)), and then use the normal match statement with the enum variants.

Upvotes: 3

Shepmaster
Shepmaster

Reputation: 430290

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

If you have a great number of variants, a macro can be used to create a parallel implementation of TryFrom automatically based on the definition of the enum:

macro_rules! back_to_enum {
    ($(#[$meta:meta])* $vis:vis enum $name:ident {
        $($(#[$vmeta:meta])* $vname:ident $(= $val:expr)?,)*
    }) => {
        $(#[$meta])*
        $vis enum $name {
            $($(#[$vmeta])* $vname $(= $val)?,)*
        }

        impl std::convert::TryFrom<i32> for $name {
            type Error = ();

            fn try_from(v: i32) -> Result<Self, Self::Error> {
                match v {
                    $(x if x == $name::$vname as i32 => Ok($name::$vname),)*
                    _ => Err(()),
                }
            }
        }
    }
}

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

See also:

Upvotes: 93

Shepmaster
Shepmaster

Reputation: 430290

If the integer you are matching on is based on the order of the variants of the enum, you can use strum to generate an iterator of the enum variants and take the correct one:

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

Upvotes: 8

Renato Zannon
Renato Zannon

Reputation: 29941

You can derive FromPrimitive. Using Rust 2018 simplified imports syntax:

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

In your Cargo.toml:

[dependencies]
num-traits = "0.2"
num-derive = "0.2"

More details in num-derive crate, see esp. sample uses in tests.

Upvotes: 76

K. Biermann
K. Biermann

Reputation: 1319

I wrote a simple macro which converts the numerical value back to the enum:

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

You can use it like this:

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

Upvotes: 2

Anders Kaseorg
Anders Kaseorg

Reputation: 3875

std::num::FromPrimitive is marked as unstable and will not be included in Rust 1.0. As a workaround, I wrote the enum_primitive crate, which exports a macro enum_from_primitive! that wraps an enum declaration and automatically adds an implementation of num::FromPrimitive (from the num crate). Example:

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

Upvotes: 17

ideasman42
ideasman42

Reputation: 47968

If you're sure the values of the integer are included in the enum, you can use std::mem::transmute.

This should be used with #[repr(..)] to control the underlying type.

Complete Example:

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

Note that unlike some of the other answers, this only requires Rust's standard library.

Upvotes: 39

wingedsubmariner
wingedsubmariner

Reputation: 13667

You can take advantage of match guards to write an equivalent, but clunkier, construction:

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 can also be used:

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

But this requires that you know the size of the enum, so you can cast to an appropriate scalar first, and will also produce undefined behavior if x is not a valid value for the enum.

Upvotes: 48

Related Questions