Increasing list of numbers in macro_rules!

I know about num-traits / strum.

The purpose of this post is mostly: can we push macro_rules to do this ? Consider the following problem:

We have an enum.
Each arm of the enum is a constant.
We want to auto derive enum <-> usize

Now, the only part I am stuck in the macro_rules! -- within the repetition, I need a way to say "give me a list of incrementing numbers" almost like an .iter().enumerate()

Again, I am aware this can be solved via procedural macros. I am just curious if we can do it via macro_rules!

use super::*;

pub trait Enum_Usize_T {
    fn to_usize(&self) -> usize;
    fn from_usize(t: usize) -> Self;
}

#[macro_export]
macro_rules! make__enum_usize {
    {
        $( #[$meta:meta] )*
        $vis:vis enum $enum_name:ident {
            $($name:id ,)*
        }
    } => {
        $( #[$meta] )*
        $vis enum $enum_name {
            $($name ,)*
        }

        impl Enum_Usize_T for $enum_name {

            fn to_usize(&self) -> usize {

            }

        }

    };

}

pub enum Foo {
    A,
    B,
    C,
    D,
    E,
    F,
}

impl Enum_Usize_T for Foo {
    fn to_usize(&self) -> usize {
        match self {
            Foo::a => 0,
            Foo::B => 1,
            Foo::C => 2,
            Foo::D => 3,
            Foo::E => 4,
            Foo::F => 5,
        }
    }

    fn from_usize(t: usize) -> Self {
        match t {
            1 => Foo::B,
            2 => Foo::C,
            3 => Foo::D,
            4 => Foo::E,
            5 => Foo::F,
            _ => Foo::A,
        }
    }
}

Surely , I dont't think it's possible to do this with the 'macro_rules!' , instead I think you should do it via Procedural Macros

Actually , there is a crate already done what you said : enum-iterator

1 Like

it's partially doable for the example code you posted.

this part is fairly straight forward to do, you just make the macro recursive invoke itself to construct tokens like (A, (1)) (B, ((1)+1)) (C, (((1)+1)+1)) etc

this part is impossible, macro_rules cannot generate tokens for the literal integer value 1, 2, etc. you can, however, implement it with a if else chain.

3 Likes

Wow, you guys are fast ... meanwhile I put together this implementation of the approach suggested by @nerditation. (not complete, not necessarily the best):

macro_rules! enumerate {
    {$cont:ident! $data:tt $n:tt} => { $cont! $data; };
    {$cont:ident! ($($data:tt)*) ($($n:tt)*) $x:tt $($xs:tt)*} => { enumerate!{$cont! ($($data)* (($($n)*+1) $x)) ($($n)*+1) $($xs)*} };
}

macro_rules! to_enum {
    { $name:ident $ty:ty; $((($index:expr) $variant:ident))* } => {
        fn $name(value: i32) -> Option<$ty> {
            #[allow(unused_parens)]
            $(if value == $index { return Some(<$ty>::$variant) })*
            None
        }
    }
}

enum Test{A, B, C}

enumerate!(to_enum!(to_enum_test Test;) (0) A B C );
// Expands to to_enum!(to_enum_test Test; ((0) A) ((0+1) B) ((0+1+1) C) )

The last one expands to:

fn to_enum_test(value: i32) -> Option<Test> {
    #[allow(unused_parens)]
    if value == 0 + 1 { return Some(<Test>::A) }
    if value == 0 + 1 + 1 { return Some(<Test>::B) }
    if value == 0 + 1 + 1 + 1 { return Some(<Test>::C) }
    None
}
1 Like

Thanks everyone; I will go procedural-macro route. I was just curious if (1) it was a limitation of my understanding of macro_rules! or (2) a limitation of macro_rules! itself. Thanks.

Well, I am just having fun now, but if you really want to use match, you can do it like this:

macro_rules! enumerate {
    {$cont:ident! $data:tt $n:tt} => { $cont! $data; };
    {$cont:ident! ($($data:tt)*) ($($n:tt)*) $x:tt $($xs:tt)*} => { enumerate!{$cont! ($($data)* (($($n)*) $x)) ($($n)*+1) $($xs)*} };
}

macro_rules! to_enum {
    { $name:ident $ty:ty; $((($index:expr) $variant:ident))* } => {
        fn $name(value: i32) -> Option<$ty> {
            $(const $variant: i32 = $index;)*
            match value {
                $($variant => Some(<$ty>::$variant),)*
                _ => None
            }
        }
    }
}

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

enumerate!(to_enum!(to_enum_test Test;) (0) A B C );

Which expands to:

fn to_enum_test(value: i32) -> Option<Test> {
    const A: i32 = 0;
    const B: i32 = 0 + 1;
    const C: i32 = 0 + 1 + 1;
    match value {
        A => Some(<Test>::A),
        B => Some(<Test>::B),
        C => Some(<Test>::C),
        _ => None,
    }
}
2 Likes

Writing a general macro is a hard job, because of Rust's flexible but not-easily-parsed syntax.
In your case where an enum has each single ident as its variants, if Copy trait is viable, here's a simple solution: Rust Playground

        impl EnumUsize for $enum_name {
            fn to_usize(&self) -> usize {
                *self as _
            }
            fn from_usize(t: usize) -> Self {
                use $enum_name::*;
                const ARR: &[$enum_name] = &[ $($name ,)* ];
                ARR[t]
            }
        }
2 Likes

Sorry, how does this work?

Enum numeric casting by copying itself.

1 Like

the usage of const is clever, as the input tokens are all unit enum variants, you don't need to generate hygienic identifiers for the constants. good job.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.