Best way to store derived information from an enum

I am working on a system where I have the beginnings of a complex enum like this:

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "kebab-case", tag = "type")]
pub enum State {
    InlineRule {
        ignore_attr_input: Option<bool>,
        inputs: Option<Vec<String>>,
        description: Option<String>,
        rule: String,
        #[serde(rename = "nextState")]
        next_state: Option<HashMap<String, String>>,
    },

    Manual {
        description: Option<String>,
        #[serde(rename = "nextState")]
        next_state: Option<String>,
    },
}

This will eventually have way more states than the ones described above. I wanted to differentiate which states are manual from the ones that are automatic. Traditionally, in other languages I used to have a class for each state and they would implement an interface that would force the implementing class to define whether the manual flag would be set for each state.

What would be the best way to achieve a similar effect based on an enum in Rust?

Meta code to illustrate the construction I'm trying to achieve:

let s: State = State::Manual { description: "Something", next_state: "next" };

if s.manual { // (or if s.is_manual() or if some_manual_indicator.contains(State::Manual) or ?)
    /// do something
} else {
    // do something else
}

And what I built so far:

impl State {
    pub fn is_manual(&self) -> bool {
        match self {
            State::Manual { .. } => true,
            State::InlineRule { .. } => false,
        }
    }
   // ...
}

Is there a better way or is this a good way to implement this?

Do you have a reason (unstated) for not using pattern matching ?

// or match if you want to handle more cases
if let State::Manual(state) = state {
    // do something
} else {
    // do something else
}

If you were to use an interface in another language, you probably want to have a look at generics and traits in Rust. This is especially true if either:

  • the number of variants will grow high;
  • or the variants are basically independent of each other except for this one property;
  • or you don't need the dynamic dispatch (i.e. you want to write generic code, but at a given point, you'll only operate on a single type when using said code).

Although I don't think it's the only way to "force" each variant to declare whether it's manual. You can also achieve that by writing an is_manual() method that returns a bool, and then pattern matching on the enum value itself. The compiler won't allow you to have non-exhaustive patterns, so if you ever add a variant to your type, you'll have to decide what to return from that method when you encounter it, in order to get your code to compile.

Thanks @erelde and @H2CO3 for your replies.

Using matching is exactly what I did, perhaps you missed my current code at the end (my fault as I should have posted it more towards the beginning of the post):

impl State {
    pub fn is_manual(&self) -> bool {
        match self {
            State::Manual { .. } => true,
            State::InlineRule { .. } => false,
        }
    }
   // ...
}

I just realized that if I generalize this to have a catch-all condition at the end, I just need to add the exceptions:

impl State {
    pub fn is_manual(&self) -> bool {
        match self {
            State::Manual { .. } => true,
            _ => false,
        }
    }
   // ...
}

And to be honest I'm quite comtempt with this approach, just wanted some validation before going ahead with this.

Technically, yes, but if you want to ensure that you make a conscious decision about every case, then it's advisable to list each variant explicitly.

If on the other hand you know that the design of your library requires that State::Manual will be the only manual kind, then you can simplify the above code further to

fn is_manual(&self) -> bool {
    matches!(self, State::Manual { .. })
}
1 Like

Having another manual state will happen but will indeed be an exception, I will think about whether to go with this whitelisting approach vs. having it implement every case explicitly. For now I think having it based on exceptions will be good and I'll make sure it's properly documented.

Now this was worth the whole thread, love learning about this macro even though I don't think I'll be able to use it, since we'll have more manual states.

All and all really really appreciate all the insight here!

Why not? Just "or" the patterns together.

fn is_manual(&self) -> bool {
    matches!(self, State::Manual { .. } | State::OtherManual { .. })
}
1 Like

Hahaha because I didn't even know this was possible :slight_smile: thanks again!