Reputation: 4163
I have this enum:
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
pub enum TheAge {
Adult,
Age(u8)
}
And the cli struct
#[derive(Parser)]
#[command(author, version, about, long_about)]
pub struct Cli {
#[arg(short, long, value_enum)]
pub age: TheAge
}
This fails with the error:
error: `#[derive(ValueEnum)]` only supports unit variants. Non-unit variants must be skipped
When I remove the Age(u8)
from the enum, this compiles.
Any tips on how to use an enum that is not unit variants?
Upvotes: 4
Views: 1733
Reputation: 10006
@cafce25's great answer returns Adult
for an invalid age. If you want an error instead, impl FromStr
rather than From<&str>
:
use clap::Parser;
use std::str::FromStr;
use anyhow::Context;
#[derive(Parser)]
#[command(author, version, about, long_about)]
pub struct Cli {
#[arg(short, long)]
pub age: TheAge
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum TheAge {
Adult,
Age(u8)
}
const LEGAL_ADULT_AGE: u8 = 18;
impl FromStr for TheAge {
type Err = anyhow::Error;
fn from_str(v: &str) -> Result<TheAge, Self::Err> {
v.parse::<u8>().map(|a| {
if a >= LEGAL_ADULT_AGE {
TheAge::Adult
} else {
TheAge::Age(a)
}
}).context("invalid age")
}
}
fn main() {
dbg!(Cli::parse_from(["", "--age", "18"]).age); // and above -> Adult
dbg!(Cli::parse_from(["", "--age", "17"]).age); // and below -> Age(17)
dbg!(Cli::parse_from(["", "--age", "anything_else"]).age); // -> Error
}
Upvotes: 0
Reputation: 179
Using rust-analyzer in VSCode I'm able to expand the ValueEnum
macro into the actual code it will generate if Age
was just a regular unit variant:
impl clap::ValueEnum for TheAge {
fn value_variants<'a>() -> &'a [Self] {
&[Self::Adult, Self::Age]
}
fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
match self {
Self::Adult => Some({ clap::builder::PossibleValue::new("adult") }),
Self::Age => Some({ clap::builder::PossibleValue::new("age") }),
_ => None,
}
}
}
So given this we can use this code to handle the fact that we want Age(u8)
:
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum TheAge {
Adult,
Age(u8),
}
impl clap::ValueEnum for TheAge {
fn value_variants<'a>() -> &'a [Self] {
&[TheAge::Adult, TheAge::Age(0)]
}
fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
match self {
Self::Adult => Some(clap::builder::PossibleValue::new("adult")),
Self::Age(_) => Some(clap::builder::PossibleValue::new("age")),
}
}
}
The only tricky bit was figuring out that using Self::Adult
will give us the error
cannot return reference to temporary value
returns a reference to data owned by the current function
I'm not really sure why using TheAge::
rather than Self::
fixes this, but it seems to work.
Upvotes: 0
Reputation: 27539
#[derive(ValueEnum)]
does not support non-unit variants, so you can't derive it.
And if you look at the required items it's sort of clear why:
impl ValueEnum for TheAge {
fn value_variants() -> &'a [Self] { todo!() }
fn to_possible_value(&self) -> Option<PossibleValue> { todo!() }
}
value_variants
is supposed to return
All possible argument values, in display order.
that's not really feasible when "all possible values" includes every single u8
(even if that's only 257 values total, it still makes a messy UI). There is no way to generically generate all values of a type so you can't #[derive(ValueEnum)]
, it might make sense to implement it by hand in some cases though (for example when the values are enum
s with only a few variants).
Instead you can implement From<&str>
for that struct and it will work:
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub enum TheAge {
Adult,
Age(u8)
}
const LEGAL_ADULT_AGE: u8 = 18;
impl From<&str> for TheAge {
fn from(v: &str) -> TheAge {
v.parse::<u8>().map_or(TheAge::Adult, |a| {
if a >= LEGAL_ADULT_AGE {
TheAge::Adult
} else {
TheAge::Age(a)
}
})
}
}
fn main() {
dbg!(Cli::parse_from(["", "--age", "18"]).age); // and above -> Adult
dbg!(Cli::parse_from(["", "--age", "17"]).age); // and below -> Age(17)
dbg!(Cli::parse_from(["", "--age", "anything_else"]).age); // -> Adult
}
Upvotes: 5