Verbose pattern matching -- can I deal with them like this?

Hi there,

I have a rich enum that I use to parse command-line arguments:

pub enum CliCommand {
    Build(BuildCommand),
    Create(CreateCommand),
}

pub enum BuildCommand {
     LocalProject(BuildInfo),
     RemoteProject(BuildInfo),
}

pub struct BuildInfo {
     pub path: String,
     pub debug: bool,
     ...
}

The important thing for me here, is that there are arguments (the BuildInfo struct) that are nested somewhat deeply into enums. I want to be able to pull those easily without having to clutter my code with a match forest.

My goal here is to pull the BuildInfo arguments from the nested enum as cleanly as possible while minding boilerplate. The BuildCommand enum only has two variants but it could grow to a lot more.

I found myself writing this code:

impl BuildCommand {
    pub fn args(&self) -> &BuildInfo {
        match self {
            BuildCommand::LocalProject(info) => info,
            BuildCommand::RemoteProject(info) => info,
    }
}

I think it's an okay solution but my issue is that it feels very silly to do an exhaustive match with redundant pattern. I would like to do something like this:

impl BuildCommand {
    pub fn args(&self) -> &BuildInfo {
        match self {
            BuildCommand::*(info: BuildInfo) => info,
    }
}

Basically match on the types of the parameters on the variant. That way if I add more variants, I can just add the signatures in the match tree. This looks a lot cleaner.

I don't think the above syntax is possible (I'm new to Rust) but what's the closest I get to that and what's the "idiomatic" kosher way to do this.

Thanks

If every variant of BuildCommand is expected to have BuildInfo, then it probably shouldn't be stored within the variants. Instead, you could make BuildCommand a struct containing the BuildInfo and a separate enum ProjectLocation.

pub enum ProjectLocation {
     LocalProject,
     RemoteProject,
}

pub struct BuildCommand {
     info: BuildInfo,
     location: ProjectLocation,
}

But if some variants may not have BuildInfo, then I don't think you can avoid a match. The decision has to be made somewhere.

2 Likes

You can use the | operator to capture the same data from multiple variants:

BuildCommand::LocalProject(info) | BuildCommand::RemoteProject(info) => info,

and use BuildCommand::* to avoid repeating the name:

LocalProject(info) | RemoteProject(info) => info,

If you use such match frequently, you can make a helper method for it that returns Option<&BuildInfo> and then use it via if let Some(info) = c.info() { … } instead of repeating the whole match.

Apart from that, don't expect to remove much more boilerplate. Rust tends to prefer syntax that is locally explicit and less "magic", even if that adds verbosity.

2 Likes

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.