Loading page…
Rust walkthroughs
Loading page…
clap::Command::mut_arg enable runtime modification of argument definitions?clap::Command::mut_arg allows you to modify an argument's definition after it has been added to a command, enabling conditional configuration, shared argument modification across commands, and dynamic help text based on runtime state. The method takes an argument ID and a closure that receives a mutable reference to the Arg, letting you call any Arg builder methods to modify its properties. This is particularly useful when arguments are defined as constants or shared across commands but need environment-specific customization, or when argument properties depend on configuration that isn't known at compile time.
use clap::{Command, Arg};
fn main() {
// Define a base argument
let input_arg = Arg::new("input")
.help("Input file")
.required(false);
let cmd = Command::new("myapp")
.arg(input_arg);
// Modify the argument after adding it
let cmd = cmd.mut_arg("input", |arg| {
arg.help("Input file (required for this operation)")
.required(true)
});
let matches = cmd.try_get_matches_from(&["myapp", "file.txt"]);
// The argument has been modified before parsing
}mut_arg provides a closure that receives &mut Arg for modification.
use clap::{Command, Arg};
use std::env;
fn main() {
let cmd = Command::new("myapp")
.arg(Arg::new("config")
.help("Configuration file"))
.arg(Arg::new("output")
.help("Output directory"));
// Modify based on environment
let cmd = if env::var("MYAPP_STRICT").is_ok() {
cmd.mut_arg("config", |arg| arg.required(true))
.mut_arg("output", |arg| arg.required(true))
} else {
cmd
};
let matches = cmd.try_get_matches_from(&["myapp", "--config", "file.toml"]);
}Arguments can be conditionally modified based on runtime state.
use clap::{Command, Arg};
fn main() {
// Define shared argument
let verbose_arg = Arg::new("verbose")
.short('v')
.long("verbose")
.help("Enable verbose output");
let cmd = Command::new("myapp")
.subcommand_required(true)
.subcommand(Command::new("build")
.about("Build the project")
.arg(verbose_arg.clone())
.arg(Arg::new("release").long("release")))
.subcommand(Command::new("test")
.about("Run tests")
.arg(verbose_arg.clone())
.arg(Arg::new("filter").long("filter")));
// Modify verbose for each subcommand differently
let cmd = cmd
.mut_subcommand("build", |subcmd| {
subcmd.mut_arg("verbose", |arg| {
arg.help("Enable verbose build output (shows compiler commands)")
})
})
.mut_subcommand("test", |subcmd| {
subcmd.mut_arg("verbose", |arg| {
arg.help("Enable verbose test output (shows all test results)")
})
});
let matches = cmd.try_get_matches_from(&["myapp", "build", "-v"]);
}mut_subcommand combined with mut_arg allows subcommand-specific argument customization.
use clap::{Command, Arg};
use std::path::PathBuf;
fn main() {
// Get home directory at runtime
let home = std::env::var("HOME")
.or_else(|_| std::env::var("USERPROFILE"))
.unwrap_or_else(|_| "/tmp".to_string());
let default_config = PathBuf::from(&home).join(".myapp/config.toml");
let cmd = Command::new("myapp")
.arg(Arg::new("config")
.long("config")
.help("Configuration file"));
// Set default value at runtime
let cmd = cmd.mut_arg("config", |arg| {
arg.default_value(default_config.to_str().unwrap())
.help(format!("Configuration file [default: {}]", default_config.display()))
});
let matches = cmd.try_get_matches_from(&["myapp"]);
if let Some(config) = matches.get_one::<String>("config") {
println!("Config: {}", config);
}
}mut_arg enables setting default values determined at runtime.
use clap::{Command, Arg};
fn main() {
let cmd = Command::new("myapp")
.arg(Arg::new("input")
.help("Input file"))
.arg(Arg::new("output")
.help("Output file"))
.arg(Arg::new("format")
.long("format")
.help("Output format"));
// Make format required if output is specified
// This logic can be applied at runtime configuration time
let cmd = cmd.mut_arg("format", |arg| {
arg.required_if_eq("output", "custom")
});
// Or based on environment configuration
let cmd = cmd.mut_arg("input", |arg| {
if std::env::var("MYAPP_BATCH").is_ok() {
arg.multiple(true).help("Input files (multiple allowed in batch mode)")
} else {
arg.multiple(false).help("Input file")
}
});
}Conditional requirements can be set dynamically.
use clap::{Command, Arg, Args, Subcommand, Parser};
#[derive(Parser)]
#[command(name = "myapp")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Build {
#[arg(short, long)]
release: bool,
#[arg(short, long)]
target: Option<String>,
},
Test {
#[arg(short, long)]
verbose: bool,
},
}
fn main() {
let mut cmd = Cli::command();
// Modify derived command
cmd = cmd.mut_subcommand("build", |subcmd| {
subcmd.mut_arg("target", |arg| {
arg.help("Build target")
.default_value("native")
})
});
// Use modified command
let matches = cmd.try_get_matches();
}mut_arg can modify arguments from derived commands.
use clap::{Command, Arg};
fn main() {
let version = env!("CARGO_PKG_VERSION");
let cmd = Command::new("myapp")
.version(version)
.arg(Arg::new("input")
.help("Input file"))
.arg(Arg::new("output")
.help("Output file"));
// Customize help text with runtime information
let cmd = cmd.mut_arg("input", |arg| {
arg.help(format!(
"Input file (supports: .txt, .json, .yaml) [version {}]",
version
))
});
// Add examples to help
let cmd = cmd.mut_arg("output", |arg| {
arg.help("Output file\n\nExamples:\n myapp input.txt output.json\n myapp data.yaml result.txt")
});
cmd.try_get_matches_from(&["myapp", "--help"]);
}Help text can incorporate runtime information.
use clap::{Command, Arg};
use std::collections::HashMap;
fn main() {
// Load configuration from file/env
let config: HashMap<String, String> = [
("default_port".to_string(), "8080".to_string()),
("allowed_formats".to_string(), "json,yaml,toml".to_string()),
].into_iter().collect();
let cmd = Command::new("server")
.arg(Arg::new("port")
.long("port")
.help("Server port"))
.arg(Arg::new("format")
.long("format")
.help("Output format"));
// Apply configuration to arguments
let cmd = cmd
.mut_arg("port", |arg| {
arg.default_value(&config["default_port"])
.help(format!("Server port [default: {}]", &config["default_port"]))
})
.mut_arg("format", |arg| {
arg.possible_values(
config["allowed_formats"].split(',')
.map(|s| s.trim())
)
.help(format!("Output format [possible values: {}]", &config["allowed_formats"]))
});
let matches = cmd.try_get_matches_from(&["server", "--help"]);
}External configuration can customize argument behavior.
use clap::{Command, Arg};
fn main() {
let cmd = Command::new("myapp")
.arg(Arg::new("json")
.long("json")
.help("Output as JSON"))
.arg(Arg::new("yaml")
.long("yaml")
.help("Output as YAML"))
.arg(Arg::new("toml")
.long("toml")
.help("Output as TOML"));
// Group these arguments as mutually exclusive
let cmd = cmd.mut_arg("json", |arg| {
arg.conflicts_with("yaml")
.conflicts_with("toml")
})
.mut_arg("yaml", |arg| {
arg.conflicts_with("json")
.conflicts_with("toml")
})
.mut_arg("toml", |arg| {
arg.conflicts_with("json")
.conflicts_with("yaml")
});
// Or use arg_group after modifying
let matches = cmd.try_get_matches_from(&["myapp", "--json", "--yaml"]);
// This will error due to conflict
}Conflicts and groups can be configured dynamically.
use clap::{Command, Arg};
use regex::Regex;
fn main() {
// Validation pattern determined at runtime
let pattern = std::env::var("MYAPP_ID_PATTERN")
.unwrap_or_else(|_| r"^[a-zA-Z0-9_-]+$".to_string());
let regex = Regex::new(&pattern).unwrap();
let cmd = Command::new("myapp")
.arg(Arg::new("id")
.required(true)
.help("Resource identifier"));
// Add validator at runtime
let cmd = cmd.mut_arg("id", |arg| {
arg.validator(move |s| {
if regex.is_valid(&s) {
Ok(())
} else {
Err(format!("Invalid ID format, must match: {}", pattern))
}
})
});
let matches = cmd.try_get_matches_from(&["myapp", "valid-id-123"]);
}Validators can be added dynamically with runtime configuration.
use clap::{Command, Arg};
use std::env;
fn main() {
let cmd = Command::new("myapp")
.arg(Arg::new("debug")
.long("debug")
.help("Enable debug mode"))
.arg(Arg::new("experimental")
.long("experimental")
.help("Enable experimental features"))
.arg(Arg::new("internal")
.long("internal")
.help("Internal debugging options"));
// Hide certain arguments based on build/environment
let is_dev = env::var("MYAPP_ENV").map(|v| v == "development").unwrap_or(false);
let cmd = cmd
.mut_arg("experimental", |arg| {
if is_dev {
arg.hide(false)
} else {
arg.hide(true) // Hidden in production
}
})
.mut_arg("internal", |arg| {
arg.hide(!is_dev) // Only show in development
});
// In production, these arguments won't show in help
// but can still be used if explicitly provided
}Arguments can be hidden based on environment conditions.
use clap::{Command, Arg, value_parser};
use std::path::PathBuf;
fn main() {
// Get allowed directories at runtime
let allowed_dirs: Vec<PathBuf> = std::env::var("MYAPP_ALLOWED_DIRS")
.map(|s| s.split(':').map(PathBuf::from).collect())
.unwrap_or_else(|_| vec![PathBuf::from("/tmp")]);
let cmd = Command::new("myapp")
.arg(Arg::new("directory")
.long("dir")
.help("Working directory"));
// Add value parser that validates the directory
let allowed_dirs_clone = allowed_dirs.clone();
let cmd = cmd.mut_arg("directory", |arg| {
arg.value_parser(move |s: &str| {
let path = PathBuf::from(s);
if allowed_dirs_clone.contains(&path) {
Ok(path)
} else {
Err(format!(
"Directory must be one of: {}",
allowed_dirs.iter()
.map(|p| p.display().to_string())
.collect::<Vec<_>>()
.join(", ")
))
}
})
});
}Value parsers can be added with runtime-determined constraints.
use clap::{Command, Arg};
fn main() {
let cmd = Command::new("myapp")
.arg(Arg::new("output")
.long("output")
.help("Output file"));
// Add aliases based on deprecation schedule
let cmd = cmd.mut_arg("output", |arg| {
// Support old name for backward compatibility
arg.visible_alias("out")
.alias("o") // Hidden alias
.alias("result") // Hidden alias
});
// Now --out works as visible alias
let matches = cmd.try_get_matches_from(&["myapp", "--out", "file.txt"]);
}Aliases can be added dynamically for backward compatibility.
mut_arg purpose:
Common use cases:
Method signature:
mut_arg(id, closure) where closure receives &mut ArgCommand for chainingRelationship to other methods:
mut_subcommand: Modify subcommands after creationmut_arg: Modify arguments within commandsWhen to use:
When NOT to use:
Key insight: mut_arg solves the problem of static argument definitions in dynamic environments. Without it, all argument properties must be known at the call site where Arg::new() happens. With mut_arg, you can define argument scaffolds early in your application and then customize them based on configuration files, environment variables, feature flags, or other runtime conditions. This is especially valuable for CLI applications that need to adapt to different deployment environments, support backward compatibility with evolving APIs, or integrate with external configuration systems. The closure-based API ensures type safety while allowing any Arg builder method to be called, making it a comprehensive solution for runtime argument modification.