Parametric counting of enum variants in Vec

Hello,

thanks to several tips & help from people in NOM gitter, i am slowly building a toy parser for ABNF according to a RFC... In the process, i stumbled upon a specific problem/pattern i assume is not that infrequent, but am not sure on elegant way to address it...

I have an enum of different items with various inner data:

enum ModuleHeaderStatements<'a> {
    YangVersion(&'a str),
    Namespace(&'a NamespaceData),    
    Prefix(&'a SomeOtherPrefixStuff),
}

I collect an array/vec of an enum instances in code...

let sub_stmts: Vec<ModuleHeaderStatements> = parse_some_stuff(...);

At this point, i need to run a check on the gathered collection (sub_stmts), and verify that there are specific numbers of various enum instances in the array...

E.g., i expect to have 2 pieces of ModuleHeaderStatements::Namespace, one piece of ModuleHeaderStatements::Prefix, and no pieces of ModuleHeaderStatements::YangVersion.

These counts i need to pass around as arguments/parameters of some "general" function, due to pattern being repeated many times for different enums/node types etc.

I have found std::mem::discriminant that i can run on already existing enum instance - to allow for e.g. building a count map/vec, or any counter-like struct to identify each of variants counts.

Yet, i find it a bit puzzling that there seems to be no way to create constant discriminant without constructing some "dummy" enum instance.

Building up artificial enum instance to be able to pass enum variant discriminant + count around as parameter feels a bit cumbersome, or is it not?

How would you address this "problem"? Defining some trait that adds/implements indexing constant for enum variants? Or do i need to get some macro based enum crate?

Sometimes you'll see people define a companion type like

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ModuleHeaderStatementsKind {
    YangVersion,
    Namespace,
    Prefix,
}

That is, a "C-like" enum whose variants correspond to those of the original type without holding any data. Such a type is trivial to construct and pass around. The big disadvantage of this approach is the duplication involved, since you need a new companion type for each enum you plan to treat in this way.

Edit: It looks like the strum crate can derive a type like this for you automatically: EnumDiscriminants in strum - Rust. It also gives you a From implementation to get the "C-like" variant associated with a given value of the original type.

2 Likes

thanks, companion enum looks good, especially with strum's already provided derive/macro, ill have a look into crate docs.

i was thinking primarily about extending orig. enum with some assoc. data but this looks cleaner/faster to execute, and maros help not to pollute the code with manual type additions...

I just realized that there's still a problem with derived enum being used as a parameter of custom functions and being related to original enum...

I cannot (or can, but do not comperehned how to if possible) define a generic function/data type for type T (e.g. ModuleHeaderStatements), that has a member field of coupled type that is being derived from T (ModuleHeaderStatementsDiscrimimant for crate enum example naming, or, ModuleHeaderStatementsKind to use your example here).

edit:
i may have overreacted, it seems to be fairly simple:

struct CoupledDemoStruct<T, S>
where T: Into<S> {
    elem: S,
    discriminant: T,
}

and used further as needed:

`CoupledDemoStruct<ModuleHeaderStatements, ModuleHeaderStatementsDiscirmimant>`

It requires defining an additional trait, but you can do better:

trait MyEnum {
    type Discriminant; // associated type
}

impl MyEnum for ModuleHeaderStatements {
    type Discriminant = ModuleHeaderStatementsDiscriminant;
}

struct CoupledDemoStruct<S> where S: MyEnum {
    elem: S,
    discriminant: S::Discriminant,
}

Note how the struct now has only one generic parameter. Actually, strum could even provide this trait and derive it as part of the EnumDiscriminants derive macro…

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.