rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

RFC: Enum trait

Open rust-highfive opened this issue 11 years ago • 18 comments
trafficstars

Issue by ghost Sunday Mar 17, 2013 at 02:56 GMT

For earlier discussion, see https://github.com/rust-lang/rust/issues/5417

This issue was labelled with: A-libs, A-traits, B-RFC in the Rust repository


Since I've been doing some work with the deriving code in libsyntax, I thought I'd solicit opinions on adding a new trait to libcore:

trait Enum {
    static fn enumerate(blk: &fn(Self) -> bool);
}

Obviously, this would be most useful for enums with only nullary variants, but other enumerable types could take advantage of it as well (bool::all_values currently serves the same purpose).

This would make it possible to write

// enum Dir { North, East, South, West }
for Enum::enumerate |dir: Dir| { ... }

instead of

for [North, East, South, West].each |dir| { ... }

A standard implementation for enums with only nullary variants would be made available through #[deriving(Enum)], and a default method (or utility trait/impl until those are working) would be provided for obtaining all the values in a vector. It might be beneficial to have a static fn cardinality() -> Option<uint> method on the trait as well.

If the trait name is too easily confused with the keyword, another option is Enumerable.

rust-highfive avatar Sep 24 '14 04:09 rust-highfive

Hello, apologies for reviving an old entry (I hope I'm not breaking any rules): what is the latest state of affairs on this subject? Are there (any plans for) traits implementable for Enums that allow you to access the tag number (preferably without consuming the enum in the process)?

My use case is a piece of code that can take any type (which it assumes/hopes to be an enum), and internally allocates an array whose length is the number of variants of the enum type - that is to say for:

enum Something { A, B, C }

An array of 3 would be allocated. To do this I have put together a trait "EnumTag" which provides the methods "tag_number(&self) -> usize" and "max_tag_number() -> usize". I thought this would be relatively trivial to implement, but then it turns out that (using the previous enum as an example):

impl EnumTag for Something { fn tag_number(&self) -> usize { *self as usize } fn max_tag_number() -> usize { Something::C as usize } }

That implementation of tag_number is now illegal, as "as usize" now consumes self, whereas from old code examples online (and prior experience) I'm lead to believe that it used to be possible to write the above?

But anyway, I digress: are there any plans/thoughts on automatically derivable traits for enums that provide features such as the EnumTag example above? (or similar?)

Please let me know if this is the wrong place to ask.

omaskery avatar Aug 28 '15 14:08 omaskery

You could self.clone() but I don't think that'd work as you want it to, either way :/

munael avatar Aug 28 '15 18:08 munael

@omaskery You just need to #[derive(Copy, Clone)] for that code to compile. Rust doesn't know that your enum is plain old data and that it can pass it by value, rather than consuming it.

Otherwise, you're on the right track, and what you're doing should compile on stable Rust today (the array you're trying to allocate will have to be a Vec).

withoutboats avatar Aug 28 '15 21:08 withoutboats

@withoutboats Fantastic! I'll try that now, thank you.

And @Enamex, thank you both for taking the time to answer!

On a similar note, I stumbled upon https://github.com/rust-lang/rfcs/blob/master/text/0639-discriminant-intrinsic.md moments after posting my initial comment (how silly I felt!) so that's interesting, too :)

omaskery avatar Aug 28 '15 22:08 omaskery

I find myself wanting to iterate over the variants of enums often, I'd love to see this get into Rust.

nixpulvis avatar Jan 13 '16 19:01 nixpulvis

Same here; in addition, I often need functionalities like Haskell's pred and succ, but have to resort to workarounds instead.

ljedrz avatar Aug 27 '16 11:08 ljedrz

You can do this with rust-enum-derive. See #[DeriveIterVariants(..)]

custom_derive! {
    #[derive(Debug, PartialEq, Eq, IterVariants(CandyVariants))]
    pub enum Candy { Musk, FruitRock, BoPeeps, LemonSherbert }
}

Links:

  • https://danielkeep.github.io/rust-custom-derive/doc/enum_derive/index.html
  • https://crates.io/crates/rust-enum-derive

nielsle avatar Aug 27 '16 11:08 nielsle

Just my 2 cents: For a personal project, I needed a similar thing, which I encapsulated into an own crate, plain_enum (see here). While it is probably not suited for everyone's needs, it may serve as a starting point or inspiration for others.

It can be used to declare a C-like enum as follows:

#[macro_use]
extern crate plain_enum;
plain_enum_mod!(module_for_enum, EnumName {
  Val1,
  Val2,
  Val3,
});

And will then allow you to do things like the following:

for value in EnumName::values() {
  // do things with value
}

let enummap = EnumName::map_from_fn(|value|
  convert_enum_value_to_mapped_value(value)
)

phimuemue avatar Feb 05 '17 17:02 phimuemue

In the domain I work in, data modeling, we need to represent each of the elements that exists in our system's universe. I'd like to make these elements an enum so that I can have verification that all possibilities are covered. But in order to do this, I need to be able to iterate through the possibilities and to count them, so that I can store my measurements, for example, in a packed array of doubles. This use case is treating the enum to make a static, perfect hash that also is typechecked.

dgrnbrg avatar Feb 07 '17 01:02 dgrnbrg

For now, you can write a macro which implements that iterator as well as the enum, I've written something similar before. Here's an example: https://is.gd/1hw4Tn

withoutboats avatar Feb 07 '17 01:02 withoutboats

Am I correct in understanding that this issue has not had an RFC written for it in the currently expected manner, and that if one was written then iterable enums have a better chance of making it intro Rust as a built-in feature?

The alternatives suggested above work for enums where the variants have no payload:

enum Foo {
    NoPayloadA,
    NoPayloadB,
}

as opposed to

enum ByteFoo {
    BytePayloadA(u8),
    BytePayloadB(u8),
}

but given that we can iterate over the values of a given enum, then we should be able to iterate over enum with payloads that can be iterated over.

For example:

enum Foo {
    FooA,
    FooB,
}

enum Bar {
    BarA,
    BarB,
}

enum Baz {
   BazFoo(Foo),
   BazBar(Bar)
}

if given the preceding code we could iterate over Foo and Bar with something like Foo::iter() and Bar::iter(), then we should also be able to iterate over all four values of Baz with something like Baz::iter().

In fact, it seems like if this was implemented in such a way that built-in types could be iterated over as well, then all 512 possible values of my ByteFoo example above should then be able to be iterated over just as easily.

Ryan1729 avatar Apr 16 '17 06:04 Ryan1729

Since iteration is only possible on enums where every variant has the same type, if this were to be a built-in feature in standard Rust it would make changing a variant's type a potentially breaking API change even on variants that no one is supposed to use. For that reason, I believe this feature should be opt-in via some kind of #[derive(...)] annotation, the same way Copy, Clone, Default, etc require a #[derive(...)] annotation because they represent important commitments in your API that can't be removed without breaking code.

The status quo is that custom derive macros have been written to achieve this for no-payload enums. Is there any reason a similar custom derive macro could not be written for all same-payload enums? I don't know of any fundamental reason why they shouldn't be able to do that.

Ixrec avatar Apr 16 '17 08:04 Ixrec

Some additional possible functionality for this trait:

trait Enum {
    // Total enum items count
    fn len(&self) -> usize; 
    // call of std::mem::discriminant() with additional typecheck.
    fn ordinal(item: Self::Item) -> usize; 
}

snuk182 avatar Jun 07 '18 14:06 snuk182

I'm a new Rust user, hopefully it's appropriate to add my 2¢ here!

It seems like Rust enums are really algebraic sum types, which is great, but C-style or particularly Java-style enums (symbolic names for a set of hard-coded constants) are not very well served. Java's Enum classes are excellent. Rust could really use something similar.

For the basic functionality, there's "custom discriminants for field-less enumerations" -- tucked away in the reference and not even mentioned in the Rust Book. It took me a while to find this!

The big missing features are conversion to and from strings, and iteration through all the constants. Iteration is the most important because it can be used to implement string conversions (i.e. iterate through the enum values to build a lookup table).

There are various crates available for doing various bits of this, but it seems like such a key feature that it would be good to pull this into the standard library. If there were a standard #[derive EnumIterator] or some such that would be great. Maybe that would just mean blessing one of the existing third-party solutions.

iainmerrick avatar Jun 29 '18 15:06 iainmerrick

guys sorry to bother but is this happen yet? something like #[derive EnumIterator]? so that we can go through our enum within the standard library?

githik999 avatar Jun 20 '22 12:06 githik999

thoughts...

  • The trait should be implemented to T, where T is enum and T's all variants are parameter-less.
    • If not limited, how will we generate the actual value during the iterations?
  • The trait should be auto-trait.
    • User may not like to implement this (and mostly never).
  • We should have fn variants() -> [Self, VARIANT_COUNT] (where VARIANT_COUNT is literal) instead of enumerate.

KisaragiEffective avatar Jun 20 '22 16:06 KisaragiEffective

An auto trait is problematic because I may not guarantee how many variants are there, their order, or anything like that. Adding a field would change VARIANT_COUNT which changes the API.

An auto trait is also problematic because new auto traits have other implications on generic code (AFAIU).

mathstuf avatar Jun 20 '22 17:06 mathstuf

  • If not limited, how will we generate the actual value during the iterations?

Hey, I am quite new to Rust but couldn't we use Default::default() for this?

dhalucario avatar Jun 21 '22 16:06 dhalucario

To add, there are some popular crates that implement some of the desired features.

The crate enum-iterator adds the Sequence trait which can be derived with #[derive(Sequence)]. This allows the consumer to get the length of the enumeration, and provides functions to get the first, last, previous and next elements. This is used to implement iteration and related concepts.

The crate enum-map adds the Enum trait which can be derived with #[derive(Enum)]. This allows the consumer to get the length of the enumeration, and provides functions to convert from and into a usize. This is used to implement the EnumMap structure, which is an efficient, compile-time key-value store for enumerations.

I also found the crate enum_traits, however this is unmaintained and not popular. It provides a collection of traits which can be derived.

There is also the Discriminant structure in the Rust standard library, but this is an opaque type and doesn't allow you to extract any useful information about the enumeration.

I think it would be great if Rust could create a trait for the standard library that mirrored the API seen in Sequence from enum-iterator and Enum from enum-map. I think a similar discussion is being discussed in regards to the num-traits crate.

scottwillmoore avatar Jun 15 '23 04:06 scottwillmoore