Enums without their "content"?


#1

I’m using enums to communicate between 2 components:

enum Command {
  Load(String),
  TogglePopup(bool),
  Reload,
}

Component1 will send these enums to component2. For example:

component2.doThis(TogglePopup(true));

This works properly, but I also want Component1 to be able to ask Component2 if it can/can’t handle these commands. Something like:

let can_do = component2.can_handle(TogglePopup);

But TogglePopup is not a valid enum. Its “content” (sorry, not sure what the exact name is), the boolean, is missing.

What’s the right approach here? Is it possible to reference an enum without its content?


#2

You can’t create an enum variant without its “content”.

You could create a second enum just for checking which one is supported:

enum SupportsCommand {
  Load,
  TogglePopup,
  Reload,
}

or make your enum explicitly dual-purpose:

enum Command {
  Load(Option<String>),
  TogglePopup(Option<bool>),
  Reload,
}

which allows TogglePopup(Some(true)) and TogglePopup(None).


#3

I like the first suggestion of @kornel better. You are really dealing with two different things here. To me it feels like Load is much like a type while Load(String) is an instance of Load. I would even suggest a different name for your second enum:

enum CommandType {
  Load,
  TogglePopup,
  Reload,
}
```

If you decide to go with that, I suggest you also implement yourself a few convenience functions e.g.:

````rust
impl Command {
    fn command_type(&self) -> CommandType {..}
    fn has_type(&self, command_type: CommandType) -> bool {..}
    // I bet there is more
    // Maybe, if you have default values for each "type"
    fn new(command_type: CommandType) -> Self {..}
    // Or if only some "types" have a sensible default
    fn new(command_type: CommandType) -> Option<Self> {..}
}
```

But on the whole, I feel very uneasy about this whole endeavour. It has you juggling type-like values at runtime. Instead of building your own tiny type system inside of Rust, you might as well take a look at the [Any](https://doc.rust-lang.org/std/any/trait.Any.html) trait which seems to do something similar. I haven't worked with it, but it allows you to wrap objects in a `Box<Any>` and later recover the original type information.

````rust 
struct LoadCommand {
    url: String,
}

struct TogglePopupCommand {
    is_enabled: bool,
}

struct ReloadCommand {}

impl Any for LoadCommand {}
// ...
```

So, this comment didn't really go as planed — I started out recommending the first option suggested by @kornel and ended up advocating against it :/ But I still feel that their second option is worse; here you don't even differentiate between type-like values and regular values.

Edit: Note that with the (closed/declined/postponed) [RFC 1450](https://github.com/rust-lang/rfcs/pull/1450) `Command::Load` would be an actual type.

#4

If “supports” relationship is static, then it may be possible to flip the entire problem around, and have Rust ensure that everything works at compile time.

trait Load { fn load(); }
trait TogglePopup {}
trait Reload {}

impl Reload for Component1 {}
impl TogglePopup for Component2 {}

and then Rust would be able to check statically whether necessary commands are implemented:

component.load();