Loading page…
Rust walkthroughs
Loading page…
clap::Arg::required and required_if for conditional argument validation?clap::Arg::required(true) marks an argument as unconditionally required—it must always be present for the command to be valid, regardless of other arguments or context. Arg::required_if(any_arg, any_value) creates conditional requirements where an argument becomes required only when another argument has a specific value, enabling sophisticated validation logic like "require --port only when --protocol is tcp" or "require --output when --format is json". The difference is fundamental: required creates static validation rules checked at parse time against the presence or absence of arguments, while required_if creates dynamic rules evaluated based on the values of other arguments, allowing CLI designs where argument requirements depend on runtime configuration choices rather than being fixed at compile time.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("myapp")
.arg(
Arg::new("input")
.short('i')
.long("input")
.required(true) // Always required
.help("Input file path")
)
.arg(
Arg::new("output")
.short('o')
.long("output")
.required(true) // Always required
.help("Output file path")
)
.get_matches_from(&["myapp", "-i", "in.txt", "-o", "out.txt"]);
println!("Input: {:?}", matches.get_one::<String>("input"));
println!("Output: {:?}", matches.get_one::<String>("output"));
}required(true) makes the argument mandatory for all invocations.
use clap::{Arg, Command};
fn main() {
let result = Command::new("myapp")
.arg(
Arg::new("input")
.short('i')
.long("input")
.required(true)
)
.try_get_matches_from(&["myapp"]); // Missing -i
match result {
Err(e) => {
println!("Error: {}", e);
// Output: error: the following required arguments were not provided:
// --input <INPUT>
}
Ok(_) => println!("Success"),
}
}Missing required arguments produce clear error messages automatically.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("myapp")
.arg(
Arg::new("format")
.short('f')
.long("format")
.value_parser(["text", "json", "xml"])
.default_value("text")
)
.arg(
Arg::new("output")
.short('o')
.long("output")
.required_if("format", "json") // Required only for JSON
.help("Output file (required for JSON format)")
)
.get_matches_from(&["myapp", "--format", "json"]);
// This would fail because --output is required for JSON
// But with --output specified:
let matches = Command::new("myapp")
.arg(
Arg::new("format")
.short('f')
.long("format")
.value_parser(["text", "json", "xml"])
)
.arg(
Arg::new("output")
.short('o')
.long("output")
.required_if("format", "json")
)
.get_matches_from(&["myapp", "--format", "json", "--output", "out.json"]);
println!("Format: {:?}", matches.get_one::<String>("format"));
println!("Output: {:?}", matches.get_one::<String>("output"));
}required_if makes an argument required when another argument has a specific value.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("server")
.arg(
Arg::new("protocol")
.short('p')
.long("protocol")
.value_parser(["tcp", "udp", "http"])
.default_value("tcp")
)
.arg(
Arg::new("port")
.long("port")
.required_if("protocol", "tcp") // Required for TCP
.required_if("protocol", "udp") // Required for UDP
.help("Port number (required for TCP/UDP)")
)
.arg(
Arg::new("url")
.long("url")
.required_if("protocol", "http") // Required for HTTP
.help("URL (required for HTTP protocol)")
)
.arg(
Arg::new("timeout")
.long("timeout")
.required_if_eq("protocol", "http") // Same as required_if
.help("Timeout for HTTP connections")
)
.get_matches_from(&["server", "--protocol", "http", "--url", "http://example.com", "--timeout", "30"]);
println!("Protocol: {:?}", matches.get_one::<String>("protocol"));
println!("URL: {:?}", matches.get_one::<String>("url"));
println!("Timeout: {:?}", matches.get_one::<String>("timeout"));
}Multiple required_if calls create OR conditions—an argument is required if any condition matches.
use clap::{Arg, Command};
fn main() {
// required_if_eq is an alias for required_if with clearer semantics
let matches = Command::new("myapp")
.arg(
Arg::new("mode")
.short('m')
.long("mode")
.value_parser(["local", "remote"])
)
.arg(
Arg::new("server")
.long("server")
.required_if_eq("mode", "remote") // Same as required_if
)
.get_matches_from(&["myapp", "--mode", "remote", "--server", "localhost"]);
println!("Mode: {:?}", matches.get_one::<String>("mode"));
println!("Server: {:?}", matches.get_one::<String>("server"));
}required_if_eq is semantically identical to required_if but may read more clearly.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("myapp")
.arg(
Arg::new("action")
.short('a')
.long("action")
.value_parser(["encrypt", "decrypt", "sign", "verify"])
)
.arg(
Arg::new("key")
.short('k')
.long("key")
.required_if("action", "encrypt") // OR
.required_if("action", "decrypt") // OR
.required_if("action", "sign") // OR
.required_if("action", "verify") // OR
.help("Key file (required for all actions)")
)
.get_matches_from(&["myapp", "--action", "encrypt", "--key", "key.pem"]);
println!("Action: {:?}", matches.get_one::<String>("action"));
println!("Key: {:?}", matches.get_one::<String>("key"));
}Each required_if call adds a condition; any match makes the argument required.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("backup")
.arg(
Arg::new("source")
.short('s')
.long("source")
.required(true) // Always required
)
.arg(
Arg::new("destination")
.short('d')
.long("destination")
.required_if("source", "stdin") // Conditionally required
.help("Destination (required if source is stdin)")
)
.get_matches_from(&["backup", "--source", "file.txt"]);
// --source is always required
// --destination is only required when --source is "stdin"
println!("Source: {:?}", matches.get_one::<String>("source"));
}Arguments can be always required AND conditionally required for additional cases.
use clap::{Arg, Command};
fn main() {
// For AND conditions, use multiple requirements on one argument
// combined with other techniques
let matches = Command::new("deploy")
.arg(
Arg::new("env")
.short('e')
.long("env")
.value_parser(["dev", "staging", "prod"])
)
.arg(
Arg::new("action")
.short('a')
.long("action")
.value_parser(["deploy", "rollback"])
)
.arg(
Arg::new("approval")
.long("approval")
.required_if("env", "prod") // Only for prod
.required_if("action", "deploy") // And only for deploy
// Result: required for (prod AND deploy)
.help("Approval ID (required for prod deployments)")
)
.get_matches_from(&[
"deploy", "--env", "prod",
"--action", "deploy",
"--approval", "APPROVE-123"
]);
println!("Env: {:?}", matches.get_one::<String>("env"));
println!("Action: {:?}", matches.get_one::<String>("action"));
println!("Approval: {:?}", matches.get_one::<String>("approval"));
}Multiple required_if calls on the same argument create AND logic for requirements.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("myapp")
.arg(
Arg::new("input")
.short('i')
.long("input")
.required_unless("config") // Required if --config not present
.conflicts_with("config")
)
.arg(
Arg::new("config")
.short('c')
.long("config")
.required_unless("input") // Required if --input not present
.conflicts_with("input")
)
.get_matches_from(&["myapp", "--input", "data.txt"]);
println!("Input: {:?}", matches.get_one::<String>("input"));
// Either --input or --config must be provided, but not both
}required_unless provides inverse conditional logic.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("build")
.arg(
Arg::new("target")
.short('t')
.long("target")
.value_parser(["debug", "release", "profile"])
)
.arg(
Arg::new("opt-level")
.long("opt-level")
.required_if_eq_any([("target", "release"), ("target", "profile")])
.help("Optimization level (required for release/profile)")
)
.get_matches_from(&["build", "--target", "release", "--opt-level", "3"]);
println!("Target: {:?}", matches.get_one::<String>("target"));
println!("Opt level: {:?}", matches.get_one::<String>("opt-level"));
}required_if_eq_any accepts multiple condition pairs with OR semantics.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("git")
.subcommand(
Command::new("push")
.arg(
Arg::new("remote")
.required(true) // Always required for push
)
.arg(
Arg::new("branch")
.required_unless("all") // Required if --all not given
)
.arg(
Arg::new("all")
.long("all")
.conflicts_with("branch")
)
)
.subcommand(
Command::new("pull")
.arg(
Arg::new("remote")
.required_if("branch", "main") // Only required for main
)
.arg(
Arg::new("branch")
)
)
.get_matches_from(&["git", "push", "origin", "main"]);
match matches.subcommand() {
Some(("push", push_matches)) => {
println!("Pushing to: {}", push_matches.get_one::<String>("remote").unwrap());
}
Some(("pull", pull_matches)) => {
println!("Pulling from: {:?}", pull_matches.get_one::<String>("remote"));
}
_ => println!("No subcommand"),
}
}Subcommands can have their own independent requirement rules.
use clap::{Arg, Command};
fn main() {
let result = Command::new("server")
.arg(
Arg::new("mode")
.long("mode")
.value_parser(["client", "server"])
.default_value("client")
)
.arg(
Arg::new("port")
.long("port")
.required_if("mode", "server")
)
.try_get_matches_from(&["server", "--mode", "server"]);
// Missing --port which is required for server mode
match result {
Err(e) => {
println!("Error: {}", e);
// Output: error: the following required arguments were not provided:
// --port <PORT>
//
// USAGE:
// server --mode <MODE> --port <PORT>
}
Ok(_) => println!("Success"),
}
}Clap generates helpful error messages showing which arguments are missing and why.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("myapp")
.arg(
Arg::new("input")
.short('i')
.long("input")
)
.arg(
Arg::new("stdin")
.long("stdin")
.conflicts_with("input")
)
.group(
clap::ArgGroup::new("source")
.args(["input", "stdin"])
.required(true) // One of the group is required
)
.arg(
Arg::new("parser")
.long("parser")
.required_if("input", "data.json") // Required for JSON input
)
.get_matches_from(&["myapp", "--input", "data.json", "--parser", "json"]);
println!("Input: {:?}", matches.get_one::<String>("input"));
println!("Parser: {:?}", matches.get_one::<String>("parser"));
}Groups provide mutual exclusion and requirement; required_if adds value-based conditions.
use clap::{Arg, Command};
fn main() {
let result = Command::new("myapp")
// First checks required arguments
.arg(
Arg::new("input")
.required(true)
)
// Then checks conflicts
.arg(
Arg::new("verbose")
.short('v')
.conflicts_with("quiet")
)
.arg(
Arg::new("quiet")
.short('q')
.conflicts_with("verbose")
)
// Then checks required_if conditions
.arg(
Arg::new("output")
.required_if("verbose", "true")
)
.try_get_matches_from(&["myapp"]);
match result {
Err(e) => println!("Validation failed: {}", e),
Ok(_) => println!("Validation passed"),
}
}Clap validates in order: required arguments, conflicts, then conditional requirements.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("tool")
.arg(
Arg::new("mode")
.short('m')
.long("mode")
.value_parser(["download", "upload", "list"])
.required(true)
)
// Download mode requirements
.arg(
Arg::new("url")
.long("url")
.required_if("mode", "download")
.required_if("mode", "upload")
.help("URL (required for download/upload)")
)
.arg(
Arg::new("file")
.long("file")
.required_if("mode", "upload")
.help("File to upload (required for upload mode)")
)
// List mode doesn't need additional args
.get_matches_from(&["tool", "--mode", "download", "--url", "https://example.com"]);
let mode = matches.get_one::<String>("mode").unwrap();
println!("Mode: {}", mode);
match mode.as_str() {
"download" | "upload" => {
println!("URL: {:?}", matches.get_one::<String>("url"));
}
"list" => {
println!("Listing...");
}
_ => {}
}
}Mode-based CLI designs use required_if to enforce mode-specific arguments.
use clap::{Arg, Command};
fn main() {
let matches = Command::new("database")
.arg(
Arg::new("action")
.short('a')
.long("action")
.value_parser(["backup", "restore", "migrate"])
.required(true)
)
.arg(
Arg::new("compress")
.long("compress")
.action(clap::ArgAction::SetTrue)
)
// Compression level required when compress is enabled
.arg(
Arg::new("compress-level")
.long("compress-level")
.required_if("compress", "true")
.value_parser(["1", "2", "3", "4", "5", "6", "7", "8", "9"])
.help("Compression level (required if --compress)")
)
.get_matches_from(&[
"database", "--action", "backup",
"--compress", "--compress-level", "9"
]);
println!("Action: {:?}", matches.get_one::<String>("action"));
println!("Compress: {:?}", matches.get_flag("compress"));
println!("Level: {:?}", matches.get_one::<String>("compress-level"));
}Feature flags can require additional configuration through required_if.
Key differences:
| Aspect | required(true) | required_if(arg, value) |
|--------|------------------|---------------------------|
| Scope | Always required | Conditionally required |
| Condition | None | Dependent on another argument's value |
| Flexibility | Static | Dynamic |
| Use case | Core arguments | Mode-dependent, feature-dependent |
Conditional requirement methods:
| Method | Meaning |
|--------|---------|
| required_if(arg, val) | Required if arg equals val |
| required_if_eq(arg, val) | Same as required_if (alias) |
| required_if_eq_any([(arg, val), ...]) | Required if any condition matches |
| required_unless(arg) | Required if arg not present |
| required_unless_eq(arg, val) | Required if arg not equal to val |
| required_unless_eq_any([(arg, val), ...]) | Required if none match |
Common patterns:
| Pattern | Implementation |
|---------|---------------|
| Mode-specific args | required_if("mode", "specific_mode") |
| Feature configuration | required_if("feature", "true") |
| Protocol-specific args | Multiple required_if for protocols needing same arg |
| Alternative requirements | required_unless for either/or |
| AND conditions | Multiple required_if on same argument |
Key insight: required(true) declares unconditional necessity—it's the baseline for essential arguments that define the operation, like input files or action commands. required_if(arg, value) declares conditional necessity—it encodes business logic about when arguments become meaningful, enabling CLI designs where the set of required arguments adapts to user choices rather than remaining fixed. The distinction mirrors the difference between "this argument is always needed" and "this argument is needed when the user has configured X." Use required for arguments that every invocation must provide; use required_if for arguments that become necessary based on runtime configuration, enabling sophisticated validation like "require --database when --mode is production" or "require --key-file when --encryption is enabled." The combination allows CLI interfaces that guide users toward complete configurations without forcing irrelevant options for unrelated modes.