45

Is there a way to extract the number of elements in an enum?

Simple example (with imaginary number_of_elements method):

enum FooBar { A = 0, B, C, };

println!("Number of items: {}", FooBar.number_of_elements());
// "Number of items: 3"

In C I'd normally do...

enum FooBar { A = 0, B, C, };
#define FOOBAR_NUMBER_OF_ITEMS (C + 1)

However the Rust equivalent to this doesn't work:

enum FooBar { A = 0, B, C, };
const FOOBAR_NUMBER_OF_ITEMS: usize = (C as usize) + 1;

// Raises an error:
//     unimplemented constant expression: enum variants

Including the last item in the enum is very inconvenient because matching enums will error if all members aren't accounted for.

enum FooBar { A = 0, B, C, FOOBAR_NUMBER_OF_ITEMS, };

Is there a way to get the number of items in an enum as a constant value?


Note: even though this isn't directly related to the question, the reason I was wanting this feature is I'm using the builder-pattern to construct a series of actions which only make sense to run once. For this reason I can use a fixed size array the size of the enum.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
ideasman42
  • 42,413
  • 44
  • 197
  • 320
  • I agree that this a feature which would be useful. Just out of interest: why do you need the number of variants? – Lukas Kalbertodt Jan 13 '17 at 15:34
  • Added explanation of why I was looking for this feature. – ideasman42 Jan 13 '17 at 15:39
  • 3
    To be honest, I find it inconvenient in C and C++ too because then I have to handle the cases in my `switch` which pollutes my code. In C and C++ I cheat with using a macro for declaring enums (black wizardry...) which also declare a few alternate things including this size which is so useful. I guess a macro would work in Rust too... but I am hoping for something better... (a custom derive could probably do the trick too, I guess...) – Matthieu M. Jan 13 '17 at 15:43
  • Agree, at least in C/C++ you can use the last element to declare a constant outside the enum, but visually close - so developers adding new members aren't so likely to forget to update it (added example). – ideasman42 Jan 14 '17 at 08:56

3 Answers3

19

Update as of 2022

There's a new function std::mem::variant_count in rust nightly version.

Example to use by rust docs.

use std::mem;

enum Void {}
enum Foo { A(&'static str), B(i32), C(i32) }

assert_eq!(mem::variant_count::<Void>(), 0);
assert_eq!(mem::variant_count::<Foo>(), 3);

assert_eq!(mem::variant_count::<Option<!>>(), 2);
assert_eq!(mem::variant_count::<Result<!, !>>(), 2);
anees
  • 1,777
  • 3
  • 17
  • 26
17

You can use procedural macros:

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

use proc_macro::TokenStream;

#[proc_macro_derive(EnumVariantCount)]
pub fn derive_enum_variant_count(input: TokenStream) -> TokenStream {
    let syn_item: syn::DeriveInput = syn::parse(input).unwrap();
    let len = match syn_item.data {
        syn::Data::Enum(enum_item) => enum_item.variants.len(),
        _ => panic!("EnumVariantCount only works on Enums"),
    };
    let expanded = quote! {
    const LENGTH: usize = #len;
        };
    expanded.into()
}

It is left as an excercise to the reader to ensure that this derive macro can be used multiple times within the same module.

To use the macro, just attach #[derive(EnumVariantCount)] to your enum. There should now be a global constant named LENGTH.

hellow
  • 12,430
  • 7
  • 56
  • 79
oli_obk
  • 28,729
  • 6
  • 82
  • 98
  • 1
    Awesome! But I think the proper way (right now) to handle erorrs, such as a non-enum body is to panic. I very much like [this post](https://cbreeden.github.io/Macros11/) to learn Macros 1.1. – Lukas Kalbertodt Jan 13 '17 at 15:57
  • Why would we panic on a non-enum? There's no difference between a single variant enum and a tuple struct. – oli_obk Jan 13 '17 at 16:21
  • 3
    Ok, maybe I didn't think about it enough. But at least *I* would expect that attaching `#[derive(EnumVariantCount)]` to a struct definition results in a compile time error. – Lukas Kalbertodt Jan 13 '17 at 16:28
  • 2
    Right... the naming is unfortunate. It's probably a mistake if it's added to a struct. – oli_obk Jan 13 '17 at 16:40
10

As an alternative (maybe this is newer since the original answer) you can use the strum crate and use the EnumCount macro. Here is their example:

use strum::{EnumCount, IntoEnumIterator};
use strum_macros::{EnumCount as EnumCountMacro, EnumIter};

#[derive(Debug, EnumCountMacro, EnumIter)]
enum Week {
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
}

assert_eq!(7, Week::COUNT);
assert_eq!(Week::iter().count(), Week::COUNT);
Nocker
  • 189
  • 3
  • 10