6

I am wondering how to implement a method for any enum that will return all variants in a Vec<T> or some kind of collection type. Something like:

pub enum MyEnum {
    EnumVariant1
    EnumVariant2
    ...
}

impl MyEnum {
    fn values(&self) -> Vec<MyEnum> {
        // do Rust stuff here
    }
}
Alex Vergara
  • 1,766
  • 1
  • 10
  • 29
  • Does this answer your question [In Rust, is there a way to iterate through the values of an enum?](/q/21371534/2189130) or were you specifically asking about how to write the macro and not just the end goal? – kmdreko Sep 11 '22 at 00:01

2 Answers2

4

There's no smart and standard solution.

An obvious one is to declare the array yourself, by repeating the variants:

static VARIANTS: &[MyEnum] = &[
    MyEnum::EnumVariant1,
    MyEnum::EnumVariant2,
];

This is a reasonable solution. When your code evolve, it's frequent you discover you don't want all variants in your static array. Or that you need several arrays.

Alternatively, if there are many elements or several enums, you may create a macro for this purpose:

macro_rules! make_enum {
    (
        $name:ident $array:ident {
            $( $variant:ident, )*
        }
    ) => {
        pub enum $name {
            $( $variant, )*
        }
        static $array: &[$name] = &[
            $( $name::$variant, )*
        ];
    }
}

make_enum! (MyEnum VARIANTS {
    EnumVariant1,
    EnumVariant2,
});

This make_enum! macro call would create both the enum and a static array called VARIANTS.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • Hey! Thanks for your response. In my code, I used to make the first aproach you've wrote, but I was wondering if there is a way to implement the feature on a generic way throught trait impl – Alex Vergara Jun 17 '21 at 19:42
  • No, this can't be done with generics. The macro is simple enough. You may implement a similar solution with a derive macro but it's a lot more work, implies other crates, and makes it difficult to adapt to different situations so I'd recommend the "simple" macro_rules. – Denys Séguret Jun 17 '21 at 19:43
  • Yes, you're completly right. And I like you're approach. Thanks for share your knowledge! – Alex Vergara Jun 17 '21 at 19:51
  • 1
    [do not upvote, this comment is useless] Thanks (again) Denys :D It's been a while. I didn't expect to see answers from you when I was looking for Rust related stuff. Hope we'll see more and more rustaceans from Lyon ;) Take care – Oliboy50 Nov 01 '21 at 09:22
3

Alternatively you can use a proc macro which makes it generic (inspired by How to get the number of elements (variants) in an enum as a constant value?):

extern crate proc_macro;
extern crate syn;
#[macro_use]
extern crate quote;

use proc_macro::TokenStream;

#[proc_macro_derive(AllVariants)]
pub fn derive_all_variants(input: TokenStream) -> TokenStream {
    let syn_item: syn::DeriveInput = syn::parse(input).unwrap();

    let variants = match syn_item.data {
        syn::Data::Enum(enum_item) => {
            enum_item.variants.into_iter().map(|v| v.ident)
        }
        _ => panic!("AllVariants only works on enums"),
    };
    let enum_name = syn_item.ident;

    let expanded = quote! {
        impl #enum_name {
            pub fn all_variants() -> &'static[#enum_name] {
                &[ #(#enum_name::#variants),* ]
            }
        }
    };
    expanded.into()
}

Then you can use it like this:

use all_variants::AllVariants;

#[derive(AllVariants, Debug)]
enum Direction {
    Left,
    Top,
    Right,
    Bottom,
}

fn main() {
    println!("{:?}", Direction::all_variants());
}

Output:

[Left, Top, Right, Bottom]

But as @Denys Séguret wrote in a previous comment, it's lot more work:

  • proc macros has to reside in their own crate (if you want to publish your work on crates.io, the proc macro crate has to be published as well);
  • it requires syn and quote crates in the proc macro (if don't want to reinvent the wheel).
yolenoyer
  • 8,797
  • 2
  • 27
  • 61