Enumerate macro repeating elements?

Anyone have a handy declarative macro trick for enumerating/indexing the repeating elements? I'm trying to generate something like this discriminant function:

pub struct Val1(u32);
pub struct Val2(String);
pub struct Val3(bool);

pub enum Foo {
    Val1(Val1),
    Val2(Val2),
    Val3(Val3)
}

impl Foo {
    pub fn discriminant(&self) -> u8 {
        match self {
            Foo::Val1(_) => 0,
            Foo::Val2(_) => 1,
            Foo::Val3(_) => 2,
        }
    }
}

I know I can count arguments like so:

macro_rules! count_args {
    ($_:tt) => { 1 };
    ($_:tt, $($tail:tt),*) => {
        1 + count_args!($($tail),*)
    }
}

but I'm having trouble getting an enumeration. The closest I can get is with nested tuples, but that isn't useful here.

Essentially, is there a way to make a macro that transforms

(Val1, Val2, Val3) => ((Val1,), (Val1, Val2), (Val1, Val2, Val3))?

I don't really understand your question.
It's quite hard to guess what the following code means

It's not the cleanest solution, but as I posted in the Rust Community Discord server, you can do something like this:

macro_rules! build_disc_match {
    (
        match on: $matchon:expr,
        variants: [$first:ident],
        branches: [$([$($branchtok:tt)+])*],
        count: $count:expr,
    ) => {
        match $matchon {
            $($($branchtok)+,)*
            Self::$first(_) => $count,
        }
    };

    (
        match on: $matchon:expr,
        variants: [$first:ident$($rest:ident)*],
        branches: [$($branch:tt)*],
        count: $count:expr,
    ) => {
        build_disc_match! {
            match on: $matchon,
            variants: [$($rest)*],
            branches: [$($branch)* [Self::$first(_) => $count]],
            count: $count + 1,
        }
    };

    ($matchon:expr => $($variant:ident),+$(,)?) => {
        build_disc_match! {
            match on: $matchon,
            variants: [$($variant)+],
            branches: [],
            count: 0,
        }
    };
}

pub struct Val1(u32);
pub struct Val2(String);
pub struct Val3(bool);

pub enum Foo {
    Val1(Val1),
    Val2(Val2),
    Val3(Val3),
}

impl Foo {
    pub fn discriminant(&self) -> u8 {
        build_disc_match!(self => Val1, Val2, Val3)
    }
}

This expands something like

build_disc_match!(self => Val1, Val2, Val3)

build_disc_match!(
    match on: self,
    variants: [Val1 Val2 Val3],
    branches: [],
    count: 0,
)

build_disc_match!(
    match on: self,
    variants: [Val2 Val3],
    branches: [[Self::Val1(_) => 0]],
    count: 0 + 1,
)

build_disc_on_match!(
    match on: self,
    variants: [Val3],
    branches: [[Self::Val1(_) => 0] [Self::Val2(_) => 0 + 1]],
    count: 0 + 1 + 1,
)

match self {
    Self::Val1(_) => 0,
    Self::Val2(_) => 0 + 1,
    Self::Val3(_) => 0 + 1 + 1,
}

This isn't super flexible if your variants aren't single field tuples, but if you need something different then I suppose maybe you could do something like take in a list of comma-separated :pats, e.g. $matchon:expr => $($variant:pat),+$(,)? and do something else when expanding the input list into match arms.

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.