'How can I use enums in structopt?

I'd like to make StructOpt work with enums such that every time a user passes -d sunday it'd parsed as a Day::Sunday:

#[macro_use]
extern crate structopt;

use std::path::PathBuf;
use structopt::StructOpt;

// My enum
enum Day {
    Sunday, Monday
}

#[derive(Debug, StructOpt)]
#[structopt(name = "example", about = "An example of StructOpt usage.")]
struct Opt {
    /// Set speed
    #[structopt(short = "s", long = "speed", default_value = "42")]
    speed: f64,
    /// Input file
    #[structopt(parse(from_os_str))]
    input: PathBuf,
    /// Day of the week
    #[structopt(short = "d", long = "day", default_value = Day::Monday)]
    day: Day,
}

fn main() {
    let opt = Opt::from_args();
    println!("{:?}", opt);
}

My current best solution is to use Option<String> as a type and pass a custom parse_day():

fn parse_day(day: &str) -> Result<Day, ParseError> {
    match day {
        "sunday" => Ok(Day::Sunday),
        _ => Ok(Day::Monday)
    }
    Err("Could not parse a day")
}


Solution 1:[1]

@kennytm's approach works, but the arg_enum! macro is a more concise way of doing it, as demonstrated in this example from structopt:

arg_enum! {
    #[derive(Debug)]
    enum Day {
        Sunday,
        Monday
    }
}

#[derive(StructOpt, Debug)]
struct Opt {
    /// Important argument.
    #[structopt(possible_values = &Day::variants(), case_insensitive = true)]
    i: Day,
}

fn main() {
    let opt = Opt::from_args();
    println!("{:?}", opt);
}

This will let you parse weekdays as Sunday or sunday.

Solution 2:[2]

The error message is:

error[E0277]: the trait bound `Day: std::str::FromStr` is not satisfied
  --> src/main.rs:22:17
   |
22 | #[derive(Debug, StructOpt)]
   |                 ^^^^^^^^^ the trait `std::str::FromStr` is not implemented for `Day`
   |
   = note: required by `std::str::FromStr::from_str`

You can fix that either by implementing FromStr for Day (see kennytm's answer), as the message suggests, or by defining a parse function for Day:

fn parse_day(src: &str) -> Result<Day, String> {
    match src {
        "sunday" => Ok(Day::Sunday),
        "monday" => Ok(Day::Monday),
        _ => Err(format!("Invalid day: {}", src))
    }
}

And specifying it with the try_from_str attribute:

/// Day of the week
#[structopt(short = "d", long = "day", parse(try_from_str = "parse_day"), default_value = "monday")]
day: Day,

Solution 3:[3]

Apparently arg_enum! has been deprecated with clap 3 coming out, see the doc here. I just refactored to use clap directly instead of StructOpts, it seems that they have been merged.

In your Cargo.toml you no longer need StructOpt anymore:

clap = { version = "3.1.17", features = ["derive"] }

In your main.rs, you will now use clap with the built in struct opt, note the documentation on the fields becomes the fields helper message value which is neat:

use clap::StructOpt;

#[derive(clap::ArgEnum, Debug, Clone)]
enum PossibleWords {
    Hello,
    World,
}

#[derive(Debug, clap::Parser, Clone)]
#[clap(long_about = "The worst Hello World!!! App in the world!")]
pub struct Args {
    /// You can't have everything in life, which word should we print?
    #[clap(arg_enum, long, default_value_t = YourEnum::Hello)]
    word: PossibleWords,

    //// Exclamation marks are included FOR FREE with the word of your choice.
    #[clap(long, default_value_t = 10)]
    number_of_exclamation_marks: u64,
}

pub fn main() {
    let args = Args::parse();
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Shepmaster
Solution 2 Peter Hall
Solution 3 Nicolas Guillaume