Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Enum trait #284

Open
rust-highfive opened this issue Sep 24, 2014 · 19 comments
Open

RFC: Enum trait #284

rust-highfive opened this issue Sep 24, 2014 · 19 comments
Labels
A-enum Enum related proposals & ideas A-traits-libstd Standard library trait related proposals & ideas T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.

Comments

@rust-highfive
Copy link

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

For earlier discussion, see rust-lang/rust#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.

@omaskery
Copy link

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.

@munael
Copy link

munael commented Aug 28, 2015

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

@withoutboats
Copy link
Contributor

@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).

@omaskery
Copy link

@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 :)

@nixpulvis
Copy link

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

@nrc nrc added T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC. labels Aug 17, 2016
@ljedrz
Copy link

ljedrz commented Aug 27, 2016

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

@nielsle
Copy link

nielsle commented Aug 27, 2016

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:

@phimuemue
Copy link

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)
)

@dgrnbrg
Copy link

dgrnbrg commented Feb 7, 2017

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.

@withoutboats
Copy link
Contributor

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

@Ryan1729
Copy link

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.

@Ixrec
Copy link
Contributor

Ixrec commented Apr 16, 2017

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.

@snuk182
Copy link

snuk182 commented Jun 7, 2018

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; 
}

@iainmerrick
Copy link

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.

@Centril Centril added A-traits-libstd Standard library trait related proposals & ideas A-enum Enum related proposals & ideas labels Nov 26, 2018
@githik999
Copy link

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?

@KisaragiEffective
Copy link

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.

@mathstuf
Copy link

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).

@dhalucario
Copy link

  • 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?

@scottwillmoore
Copy link

scottwillmoore commented Jun 15, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-enum Enum related proposals & ideas A-traits-libstd Standard library trait related proposals & ideas T-lang Relevant to the language team, which will review and decide on the RFC. T-libs-api Relevant to the library API team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests