admin管理员组

文章数量:1398826

I have an enum representing various actions that can be taken. However, some of its variants only makes sense in certain contexts, known at compile-time. To limit access to certain variants, I would like to add a const generic parameter to my enum like in the code below.

enum Action<const COND1: bool, const COND2: bool> {
    A,
    B,
    C<enable if COND1>,
    D<enable if COND2>,
}

let action1 = Action<false, false>::A;
match action1 {
    Action<false, false>::A => {}
    Action<false, false>::B => {}
}

let action2 = Action<true, false>::A;
match action2 {
    Action<true, false>::A => {}
    Action<true, false>::B => {}
    Action<true, false>::C => {}
}

let action3 = Action<false, true>::A;
match action3 {
    Action<false, true>::A => {}
    Action<false, true>::B => {}
    Action<false, true>::D => {}
}

Is this possible to achieve in rust?

I could create a separate enum for each combination, but that would result in a lot of duplicated code.

I have an enum representing various actions that can be taken. However, some of its variants only makes sense in certain contexts, known at compile-time. To limit access to certain variants, I would like to add a const generic parameter to my enum like in the code below.

enum Action<const COND1: bool, const COND2: bool> {
    A,
    B,
    C<enable if COND1>,
    D<enable if COND2>,
}

let action1 = Action<false, false>::A;
match action1 {
    Action<false, false>::A => {}
    Action<false, false>::B => {}
}

let action2 = Action<true, false>::A;
match action2 {
    Action<true, false>::A => {}
    Action<true, false>::B => {}
    Action<true, false>::C => {}
}

let action3 = Action<false, true>::A;
match action3 {
    Action<false, true>::A => {}
    Action<false, true>::B => {}
    Action<false, true>::D => {}
}

Is this possible to achieve in rust?

I could create a separate enum for each combination, but that would result in a lot of duplicated code.

Share Improve this question asked Mar 26 at 22:30 FredFred 4575 silver badges16 bronze badges 3
  • What is your goal? Improving codegen or improving code matching on the enum? – Chayim Friedman Commented Mar 26 at 23:24
  • The example given doesn't do much. I'm actually creating a "schema enum" made up of nested enums that describes the functionality of an object. I then use serde to convert this schema to json. This allows me to create new objects by de-serializing a json file. The schema is then pattern matched when executed to produce an effect described by the schema. I'm intending to use rusts type system to both make sure only valid schemas can be represented and that all schema combinations are accounted for when pattern-matching. – Fred Commented Mar 27 at 11:17
  • Wouldn't a feature gate be something that could help here? Since the conditions are known at compile time, you could have a #[non_exhaustive] enum with feature gates on certain variants. – JarroVGIT Commented Mar 28 at 7:24
Add a comment  | 

2 Answers 2

Reset to default 5

The never type can be used to "erase" variants that use a generic type, but this usage is not yet stabilized. On nightly:

#![feature(never_type)]

enum Action<C, D> {
    A,
    B,
    C(C),
    D(D),
}

You can use the () type to enable a variant and the ! type to disable it, so Action<!, ()> would make the C variant uninhabitable; matching on such a type would therefore not require the C variant be considered.


Until ! is stabilized for this use, you can use an uninhabitable type like Infallible (or any other empty enum) as a substitute for !. Here is one way that you could do it:

type Enable = ();
type Disable = std::convert::Infallible;

enum Action<C, D> {
    A,
    B,
    C(C),
    D(D),
}

Now Action<Disable, Enable> has the same properties as Action<!, ()> would on nightly. Note the compiler is smart enough to figure out that the C variant is uninhabitable even without usage of ! and so, like on nightly, match does not require listing the uninhabitable variants.

(Once ! is stabilized you can keep the same source-compatible API by changing the Disable alias to be ! instead of Infallible.)

In the end, I solved my problem by implementing separate enums with a variant pointing to another enum:

#[derive(Clone, Serialize, Deserialize)]
enum Action1 {
    A,
    B,
}

#[derive(Clone, Serialize, Deserialize)]
enum Action2 {
    C,
    #[serde(untagged)]
    Other(Action1),
}

#[derive(Clone, Serialize, Deserialize)]
enum Action3 {
    D,
    #[serde(untagged)]
    Other(Action2),
}

By using #[serde(untagged)], that enum gets serialized as if the enum members inside the "Other" variant is part of the enum, which is what I actually wanted to accomplish.

本文标签: rustenabledisable enum variant with const generic parameterStack Overflow