Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the number of elements (variants) in an enum as a constant value?

Tags:

enums

rust

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.

like image 899
ideasman42 Avatar asked Jan 13 '17 15:01

ideasman42


People also ask

How do you find the number of elements in an enum?

Use the len() class to get the number of elements in an enum, e.g. len(Color) . The len() function returns the length (the number of items) of an object and can directly be passed an enum.

How do you find the length of an enum?

To get the length of an enum: Use the Object. keys() method to get an array containing the enum's keys. Access the length property on the array.

Is enum type or constant?

An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it.


3 Answers

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.

like image 194
oli_obk Avatar answered Oct 11 '22 01:10

oli_obk


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);
like image 8
Nocker Avatar answered Oct 11 '22 01:10

Nocker


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);
like image 4
anees Avatar answered Oct 11 '22 01:10

anees