Loading page…
Rust walkthroughs
Loading page…
{"args":{"content":"# How does clap::ValueEnum derive macro differ from implementing clap::Parser manually for enum arguments?
ValueEnum is a derive macro specifically for enums that enables them to be used as command-line argument values, automatically implementing the ValueEnum trait which provides variant name conversion, possible values enumeration, and help text generation. Implementing Parser manually for enums requires writing the parsing logic yourself, including handling variant matching, error reporting for invalid values, and maintaining the list of valid variants. The ValueEnum derive is declarative and handles all the boilerplate automatically, while manual Parser implementation gives you full control over parsing behavior at the cost of significantly more code.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum)]
mode: Mode,
}
#[derive(Clone, ValueEnum)]
enum Mode {
Fast,
Balanced,
Thorough,
}
fn main() {
let cli = Cli::parse();
println!(\"Mode: {:?}\", cli.mode);
}
// Usage:
// ./program fast -> Mode::Fast
// ./program balanced -> Mode::Balanced
// ./program FAST -> Mode::Fast (case-insensitive)
// ./program invalid -> Error: \"invalid value\"ValueEnum automatically handles case-insensitive matching and validation.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum)]
format: OutputFormat,
}
#[derive(Clone, ValueEnum)]
enum OutputFormat {
Json,
Yaml,
Toml,
Xml,
}
// Running with --help shows:
// --format <FORMAT>
// Possible values:
// - json
// - yaml
// - toml
// - xmlValueEnum automatically generates possible values in help text.
use clap::{Parser, ValueEnum};
use std::str::FromStr;
#[derive(Parser)]
struct Cli {
#[arg(value_parser = parse_mode)]
mode: Mode,
}
#[derive(Clone, Debug)]
enum Mode {
Fast,
Balanced,
Thorough,
}
// Manual implementation requires FromStr
impl FromStr for Mode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
\"fast\" => Ok(Mode::Fast),
\"balanced\" => Ok(Mode::Balanced),
\"thorough\" => Ok(Mode::Thorough),
_ => Err(format!(\"invalid value: '{}'. Expected one of: fast, balanced, thorough\", s)),
}
}
}
fn parse_mode(s: &str) -> Result<Mode, String> {
Mode::from_str(s)
}
fn main() {
let cli = Cli::parse();
println!(\"Mode: {:?}\", cli.mode);
}Manual implementation requires FromStr or custom parser function.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum)]
level: LogLevel,
}
#[derive(Clone, ValueEnum)]
enum LogLevel {
#[value(name = \"debug\", alias = \"d\")]
Debug,
#[value(name = \"info\", alias = \"i\")]
Info,
#[value(name = \"warn\", alias = \"w\")]
Warning,
#[value(name = \"error\", alias = \"e\")]
Error,
#[value(name = \"off\")]
Off,
}
fn main() {
let cli = Cli::parse();
println!(\"Level: {:?}\", cli.level);
}
// All of these work:
// --level debug
// --level d (alias)
// --level DEBUG (case-insensitive)ValueEnum supports aliases and custom names via #[value] attribute.
use clap::{Parser, IntoApp};
use std::str::FromStr;
#[derive(Parser)]
struct Cli {
#[arg(value_parser = parse_level)]
level: LogLevel,
}
#[derive(Clone, Debug)]
enum LogLevel {
Debug,
Info,
Warning,
Error,
Off,
}
impl FromStr for LogLevel {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
\"debug\" | \"d\" => Ok(LogLevel::Debug),
\"info\" | \"i\" => Ok(LogLevel::Info),
\"warn\" | \"w\" => Ok(LogLevel::Warning),
\"error\" | \"e\" => Ok(LogLevel::Error),
\"off\" => Ok(LogLevel::Off),
_ => Err(format!(\"invalid value: '{}'\", s)),
}
}
}
fn parse_level(s: &str) -> Result<LogLevel, String> {
// Would need to manually list possible values for help
LogLevel::from_str(s)
}
fn main() {
let cli = Cli::parse();
println!(\"Level: {:?}\", cli.level);
}Manual implementation handles aliases but requires custom error messages.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum)]
status: Status,
}
#[derive(Clone, ValueEnum)]
enum Status {
Active,
Inactive,
#[value(skip)]
Internal, // Not available as CLI argument
}
fn main() {
let cli = Cli::parse();
println!(\"Status: {:?}\", cli.status);
}
// --status active -> Status::Active
// --status internal -> Error: not a valid value#[value(skip)] excludes variants from CLI arguments.
use clap::{Parser};
use std::str::FromStr;
#[derive(Parser)]
struct Cli {
#[arg(value_parser = parse_status)]
status: Status,
}
#[derive(Clone, Debug)]
enum Status {
Active,
Inactive,
Internal, // Must manually exclude
}
impl FromStr for Status {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
\"active\" => Ok(Status::Active),
\"inactive\" => Ok(Status::Inactive),
// Internal not included
_ => Err(format!(\"invalid value: '{}'\", s)),
}
}
}
fn parse_status(s: &str) -> Result<Status, String> {
Status::from_str(s)
}
fn main() {
let cli = Cli::parse();
println!(\"Status: {:?}\", cli.status);
}Manual implementation requires explicitly excluding variants.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum, ignore_case = true)] // Default is true
algorithm: Algorithm,
}
#[derive(Clone, ValueEnum)]
enum Algorithm {
Aes,
Des,
Rsa,
}
fn main() {
let cli = Cli::parse();
println!(\"Algorithm: {:?}\", cli.algorithm);
}
// All of these work:
// --algorithm aes
// --algorithm AES
// --algorithm AesValueEnum defaults to case-insensitive matching.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum, ignore_case = false)]
branch: Branch,
}
#[derive(Clone, ValueEnum)]
enum Branch {
#[value(name = \"main\")]
Main,
#[value(name = \"develop\")]
Develop,
}
fn main() {
let cli = Cli::parse();
println!(\"Branch: {:?}\", cli.branch);
}
// --branch main -> Branch::Main
// --branch MAIN -> Error (case-sensitive)Set ignore_case = false for case-sensitive matching.
use clap::{Parser};
use std::str::FromStr;
#[derive(Parser)]
struct Cli {
#[arg(value_parser = parse_branch)]
branch: Branch,
}
#[derive(Clone, Debug)]
enum Branch {
Main,
Develop,
}
fn parse_branch(s: &str) -> Result<Branch, String> {
// Case-sensitive: must match exactly
match s {
\"main\" => Ok(Branch::Main),
\"develop\" => Ok(Branch::Develop),
_ => Err(format!(\"invalid branch: '{}'\", s)),
}
}
fn main() {
let cli = Cli::parse();
println!(\"Branch: {:?}\", cli.branch);
}Manual implementation gives complete control over case handling.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum)]
color: ColorMode,
}
#[derive(Clone, ValueEnum)]
enum ColorMode {
Always,
Auto,
Never,
}
fn main() {
let cli = Cli::parse();
println!(\"Color: {:?}\", cli.color);
// Possible values accessible
let possible: Vec<&str> = ColorMode::value_variants()
.iter()
.map(|v| v.to_possible_value().unwrap().get_name())
.collect();
println!(\"Possible values: {:?}\", possible);
}ValueEnum provides value_variants() and to_possible_value() methods.
use clap::{Parser, builder::PossibleValue};
use std::str::FromStr;
#[derive(Parser)]
struct Cli {
#[arg(value_parser = parse_color)]
color: ColorMode,
}
#[derive(Clone, Debug)]
enum ColorMode {
Always,
Auto,
Never,
}
impl FromStr for ColorMode {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
\"always\" => Ok(ColorMode::Always),
\"auto\" => Ok(ColorMode::Auto),
\"never\" => Ok(ColorMode::Never),
_ => Err(format!(\"invalid value: '{}'\", s)),
}
}
}
fn parse_color(s: &str) -> Result<ColorMode, String> {
ColorMode::from_str(s)
}
// Help would need to be manually customized to show possible values
fn main() {
let cli = Cli::parse();
println!(\"Color: {:?}\", cli.color);
}Manual implementation requires extra work for help text.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum, default_value = \"auto\")]
color: ColorMode,
}
#[derive(Clone, ValueEnum, Default)]
enum ColorMode {
Always,
#[default]
Auto,
Never,
}
fn main() {
let cli = Cli::parse();
println!(\"Color: {:?}\", cli.color);
}
// No argument -> ColorMode::Auto (default)
// --color always -> ColorMode::AlwaysValueEnum works with #[default] attribute for default variants.
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum)]
compression: Compression,
}
#[derive(Clone, ValueEnum)]
enum Compression {
#[value(name = \"gzip\")]
Gzip,
#[value(name = \"bzip2\")]
Bzip2,
#[value(name = \"lz4\")]
Lz4,
#[value(name = \"zstd\")]
Zstd,
#[value(name = \"none\", aliases = [\"raw\", \"skip\"])]
None,
}
fn main() {
let cli = Cli::parse();
match cli.compression {
Compression::Gzip => println!(\"Using gzip\"),
Compression::Bzip2 => println!(\"Using bzip2\"),
Compression::Lz4 => println!(\"Using lz4\"),
Compression::Zstd => println!(\"Using zstd\"),
Compression::None => println!(\"No compression\"),
}
}ValueEnum supports multiple aliases per variant.
use clap::{Parser, Subcommand, ValueEnum};
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Process {
#[arg(value_enum)]
format: Format,
},
Convert {
#[arg(value_enum)]
from: Format,
#[arg(value_enum)]
to: Format,
},
}
#[derive(Clone, ValueEnum)]
enum Format {
Json,
Yaml,
Toml,
}
fn main() {
let cli = Cli::parse();
match cli.command {
Command::Process { format } => println!(\"Processing {:?}\", format),
Command::Convert { from, to } => println!(\"Converting {:?} to {:?}\", from, to),
}
}ValueEnum works in subcommands and can be used multiple times.
use clap::Parser;
use std::str::FromStr;
#[derive(Parser)]
struct Cli {
#[arg(value_parser = parse_port)]
port: Port,
}
#[derive(Clone, Debug)]
enum Port {
Number(u16),
Auto,
}
impl FromStr for Port {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
\"auto\" => Ok(Port::Auto),
num => {
let n: u16 = num.parse()
.map_err(|_| format!(\"invalid port: '{}'\", s))?;
Ok(Port::Number(n))
}
}
}
}
fn parse_port(s: &str) -> Result<Port, String> {
Port::from_str(s)
}
fn main() {
let cli = Cli::parse();
println!(\"Port: {:?}\", cli.port);
}
// --port auto -> Port::Auto
// --port 8080 -> Port::Number(8080)
// --port invalid -> ErrorManual implementation enables complex variant values (not just unit variants).
// ValueEnum:
// - Automatic case-insensitive matching
// - Automatic help text with possible values
// - Automatic error messages with valid options
// - #[value] attribute for custom names and aliases
// - #[value(skip)] to exclude variants
// - Only works with unit variants (no data)
// - Must derive Clone
// - Declarative, minimal code
// Manual Parser:
// - Full control over parsing logic
// - Can use variants with data
// - Can implement custom validation
// - Must write error messages manually
// - Must maintain possible values list manually
// - More code but more flexibility
// - Case sensitivity is your choice
use clap::{Parser, ValueEnum};
#[derive(Parser)]
struct Cli {
#[arg(value_enum)]
simple: SimpleEnum, // ValueEnum handles everything
#[arg(value_parser = parse_complex)]
complex: ComplexEnum, // Manual parser for flexibility
}
#[derive(Clone, ValueEnum)]
enum SimpleEnum {
A, B, C,
}
#[derive(Clone, Debug)]
enum ComplexEnum {
Named(String),
Number(u32),
Default,
}
fn parse_complex(s: &str) -> Result<ComplexEnum, String> {
if s == \"default\" {
return Ok(ComplexEnum::Default);
}
if let Ok(n) = s.parse::<u32>() {
return Ok(ComplexEnum::Number(n));
}
Ok(ComplexEnum::Named(s.to_string()))
}
fn main() {
let cli = Cli::parse();
println!(\"Simple: {:?}, Complex: {:?}\", cli.simple, cli.complex);
}Choose ValueEnum for simple enums; manual parsing for complex variants.
ValueEnum advantages:
#[value] attributes for names, aliases, and skipManual Parser advantages:
Clone derive if not neededWhen to use ValueEnum:
When to use manual Parser:
Key limitation of ValueEnum:
Only works with unit variants (no data). Variants like Name(String) or Value(u32) cannot use ValueEnum and must use manual parsing.
Implementation differences:
ValueEnum implements the clap::ValueEnum trait automaticallyFromStr trait or custom parser functionValueEnum provides value_variants() and to_possible_value() for introspectionKey insight: The ValueEnum derive macro is the right choice for the common case of simple enumerations used as CLI arguments. It handles all the boilerplate: case-insensitive matching, help text generation, error messages with valid options, and alias handling. Manual Parser implementation becomes necessary when you need variants with data, complex validation logic, or want complete control over error messages. The ValueEnum macro exists because 90% of enum arguments in CLI applications are simple unit variants, and the macro eliminates the repetitive FromStr implementation and help text maintenance that would otherwise be required for each one.","path":"/articles/298_clap_valueenum_vs_parser.md"},"tool_call":"file.create"}