How does clap::Arg::default_value_if set conditional defaults based on other argument values?
default_value_if allows an argument's default value to depend on the presence or value of another argument, enabling context-sensitive defaults that adapt to user input without requiring manual validation code. This declarative approach simplifies command-line argument handling by encoding conditional logic directly into the argument definition.
Basic Conditional Defaults
use clap::{Arg, Command};
fn basic_conditional() -> Command {
Command::new("example")
.arg(
Arg::new("format")
.long("format")
.value_parser(["json", "yaml", "toml"])
.default_value("json")
)
.arg(
Arg::new("output")
.long("output")
.default_value_if("format", "json", "output.json")
.default_value_if("format", "yaml", "output.yaml")
.default_value_if("format", "toml", "output.toml")
)
}The output argument gets a different default depending on the format argument's value.
Simple Condition Syntax
use clap::{Arg, Command};
fn condition_syntax() -> Command {
Command::new("example")
.arg(
Arg::new("mode")
.long("mode")
.value_parser(["fast", "thorough"])
)
.arg(
Arg::new("threads")
.long("threads")
// If mode is "fast", default threads to "4"
.default_value_if("mode", "fast", "4")
// If mode is "thorough", default threads to "16"
.default_value_if("mode", "thorough", "16")
)
}Each default_value_if call specifies: argument name, expected value, and default to apply.
Presence-Based Conditions
use clap::{Arg, Command};
fn presence_condition() -> Command {
Command::new("example")
.arg(
Arg::new("verbose")
.long("verbose")
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("log-level")
.long("log-level")
// If "verbose" is present, default to "debug"
.default_value_if("verbose", "true", "debug")
// Otherwise use this default
.default_value("info")
)
}When verbose flag is present, log-level defaults to "debug" instead of "info".
Multiple Conditions with Fallbacks
use clap::{Arg, Command};
fn multiple_conditions() -> Command {
Command::new("builder")
.arg(
Arg::new("compiler")
.long("compiler")
.value_parser(["gcc", "clang", "msvc"])
.default_value("gcc")
)
.arg(
Arg::new("flags")
.long("flags")
// Chain of conditions evaluated in order
.default_value_if("compiler", "gcc", "-O2 -Wall")
.default_value_if("compiler", "clang", "-O2 -Weverything")
.default_value_if("compiler", "msvc", "/O2 /W4")
)
}Conditions are evaluated in order; the first matching condition provides the default.
Condition with Any Value
use clap::{Arg, Command};
fn any_value_condition() -> Command {
Command::new("example")
.arg(
Arg::new("config")
.long("config")
)
.arg(
Arg::new("validate")
.long("validate")
.action(clap::ArgAction::SetTrue)
// If config has any value, set validate to true
.default_value_if("config", None, "true")
)
}Using None as the condition value matches any value of the referenced argument.
Condition Negation
use clap::{Arg, Command};
fn negated_condition() -> Command {
Command::new("example")
.arg(
Arg::new("input")
.long("input")
.required(true)
)
.arg(
Arg::new("output")
.long("output")
// Default output to stdout if no explicit output
.default_value_if("input", None, "-")
)
.arg(
Arg::new("compress")
.long("compress")
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("level")
.long("level")
// Only set default if compress is NOT present
// (default_value_if only sets when condition matches)
.default_value("6")
)
}The condition checks for presence or specific values; absence leaves the argument unset.
Complex Conditional Logic
use clap::{Arg, Command};
fn complex_conditions() -> Command {
Command::new("server")
.arg(
Arg::new("protocol")
.long("protocol")
.value_parser(["http", "https"])
.default_value("http")
)
.arg(
Arg::new("port")
.long("port")
.default_value_if("protocol", "http", "80")
.default_value_if("protocol", "https", "443")
)
.arg(
Arg::new("timeout")
.long("timeout")
// Default timeout depends on protocol
.default_value_if("protocol", "http", "30")
.default_value_if("protocol", "https", "60")
)
.arg(
Arg::new("retries")
.long("retries")
.default_value("3")
)
}Multiple arguments can have conditional defaults based on the same parent argument.
Priority Ordering
use clap::{Arg, Command};
fn priority_ordering() -> Command {
Command::new("example")
.arg(
Arg::new("env")
.long("env")
.value_parser(["dev", "staging", "prod"])
)
.arg(
Arg::new("debug")
.long("debug")
// First matching condition wins
.default_value_if("env", "dev", "true")
.default_value_if("env", "staging", "true")
.default_value_if("env", "prod", "false")
// Fallback if no env specified
.default_value("false")
)
}Conditions are evaluated in definition order; first match wins.
Interacting with Required Arguments
use clap::{Arg, Command};
fn with_required() -> Command {
Command::new("example")
.arg(
Arg::new("input")
.required(true)
)
.arg(
Arg::new("format")
.long("format")
.value_parser(["auto", "json", "csv"])
.default_value("auto")
)
.arg(
Arg::new("delimiter")
.long("delimiter")
// Only relevant when format is csv
.default_value_if("format", "csv", ",")
// If format is auto, try to detect
.default_value_if("format", "auto", "detect")
)
}Conditional defaults work alongside required arguments.
Integration with ArgAction
use clap::{Arg, ArgAction, Command};
fn with_actions() -> Command {
Command::new("example")
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.action(ArgAction::Count)
)
.arg(
Arg::new("log-level")
.long("log-level")
.value_parser(["error", "warn", "info", "debug", "trace"])
// Different defaults based on verbosity count
.default_value_if("verbose", None, "info")
)
.arg(
Arg::new("progress")
.long("progress")
.action(ArgAction::SetTrue)
// Show progress bar if verbose
.default_value_if("verbose", None, "true")
)
}Conditional defaults work with ArgAction values, checking presence via None.
Real-World Example: Build Tool
use clap::{Arg, Command};
fn build_tool() -> Command {
Command::new("build")
.about("Build tool with smart defaults")
.arg(
Arg::new("target")
.long("target")
.short('t')
.value_parser(["debug", "release", "test"])
.default_value("debug")
)
.arg(
Arg::new("optimization")
.long("opt")
.short('O')
.default_value_if("target", "debug", "0")
.default_value_if("target", "release", "3")
.default_value_if("target", "test", "0")
)
.arg(
Arg::new("symbols")
.long("symbols")
.default_value_if("target", "debug", "full")
.default_value_if("target", "release", "none")
.default_value_if("target", "test", "full")
)
.arg(
Arg::new("lto")
.long("lto")
.default_value_if("target", "release", "thin")
// No LTO for debug/test targets
)
.arg(
Arg::new("parallel")
.long("parallel")
.default_value_if("target", "debug", "1")
.default_value_if("target", "release", "auto")
.default_value_if("target", "test", "auto")
)
}Build targets automatically configure appropriate defaults for optimization, symbols, LTO, and parallelism.
Real-World Example: Database Client
use clap::{Arg, Command};
fn database_client() -> Command {
Command::new("db-cli")
.about("Database client with connection-aware defaults")
.arg(
Arg::new("database")
.long("database")
.value_parser(["postgres", "mysql", "sqlite"])
.required(true)
)
.arg(
Arg::new("port")
.long("port")
.default_value_if("database", "postgres", "5432")
.default_value_if("database", "mysql", "3306")
.default_value_if("database", "sqlite", "")
)
.arg(
Arg::new("host")
.long("host")
.default_value_if("database", "postgres", "localhost")
.default_value_if("database", "mysql", "localhost")
.default_value_if("database", "sqlite", "")
)
.arg(
Arg::new("timeout")
.long("timeout")
.default_value_if("database", "postgres", "30")
.default_value_if("database", "mysql", "30")
.default_value_if("database", "sqlite", "0")
)
}Database type determines sensible defaults for port, host, and timeout.
Debugging Conditional Defaults
use clap::{Arg, Command};
fn debug_conditions() {
let matches = Command::new("example")
.arg(
Arg::new("mode")
.long("mode")
.value_parser(["local", "remote"])
.default_value("local")
)
.arg(
Arg::new("path")
.long("path")
.default_value_if("mode", "local", "/var/local")
.default_value_if("mode", "remote", "/var/remote")
)
.get_matches_from(&["example", "--mode", "local"]);
// Check what values were resolved
let mode = matches.get_one::<String>("mode").unwrap();
let path = matches.get_one::<String>("path").unwrap();
println!("Mode: {}", mode); // "local"
println!("Path: {}", path); // "/var/local"
// The path was set by default_value_if
// User didn't provide --path, but it has a value
}Arguments with conditional defaults appear to have values even when not explicitly provided.
Conditional vs Explicit Values
use clap::{Arg, Command};
fn conditional_vs_explicit() {
let matches = Command::new("example")
.arg(
Arg::new("format")
.long("format")
.value_parser(["json", "xml"])
.default_value("json")
)
.arg(
Arg::new("indent")
.long("indent")
.default_value_if("format", "json", "2")
.default_value_if("format", "xml", "4")
)
.get_matches_from(&["example", "--format", "json", "--indent", "4"]);
// Explicit --indent overrides the conditional default
let indent = matches.get_one::<String>("indent").unwrap();
assert_eq!(indent, "4"); // Explicit, not "2"
// Conditional default only applies when no explicit value
}User-provided values always take precedence over conditional defaults.
Combining with Validation
use clap::{Arg, Command};
fn with_validation() -> Command {
Command::new("example")
.arg(
Arg::new("level")
.long("level")
.value_parser(["1", "2", "3"])
.default_value("1")
)
.arg(
Arg::new("workers")
.long("workers")
.default_value_if("level", "1", "1")
.default_value_if("level", "2", "4")
.default_value_if("level", "3", "16")
// Validate that workers is reasonable
.value_parser(clap::value_parser!(u32).range(1..=32))
)
.arg(
Arg::new("timeout")
.long("timeout")
// Defaults depend on level
.default_value_if("level", "1", "60")
.default_value_if("level", "2", "120")
.default_value_if("level", "3", "300")
.value_parser(clap::value_parser!(u64))
)
}Conditional defaults work with value parsers for validation.
Multiple Argument Dependencies
use clap::{Arg, Command};
fn multiple_dependencies() -> Command {
Command::new("example")
.arg(
Arg::new("input-format")
.long("input-format")
.value_parser(["json", "yaml"])
.default_value("json")
)
.arg(
Arg::new("output-format")
.long("output-format")
.value_parser(["json", "yaml", "csv"])
.default_value_if("input-format", "json", "json")
.default_value_if("input-format", "yaml", "yaml")
)
.arg(
Arg::new("pretty")
.long("pretty")
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("indent")
.long("indent")
// Only applies if pretty is set
.default_value_if("pretty", "true", "2")
)
}Conditions can chain: output-format depends on input-format, indent depends on pretty.
Checking Conditional vs Unconditional Defaults
use clap::{Arg, Command};
fn default_types() {
// Unconditional default: always applied
let cmd = Command::new("example")
.arg(
Arg::new("value")
.long("value")
.default_value("42") // Always defaults to 42
);
// Conditional default: depends on condition
let cmd = Command::new("example")
.arg(
Arg::new("mode")
.long("mode")
.value_parser(["a", "b"])
)
.arg(
Arg::new("value")
.long("value")
.default_value_if("mode", "a", "10") // Only if mode is "a"
.default_value_if("mode", "b", "20") // Only if mode is "b"
);
// If mode is not specified, value has no default!
// Combine with unconditional default for fallback:
let cmd = Command::new("example")
.arg(
Arg::new("mode")
.long("mode")
.value_parser(["a", "b"])
)
.arg(
Arg::new("value")
.long("value")
.default_value_if("mode", "a", "10")
.default_value_if("mode", "b", "20")
.default_value("0") // Fallback for no mode
);
}Combine conditional and unconditional defaults to ensure a value is always present.
Edge Cases and Behavior
use clap::{Arg, Command};
fn edge_cases() {
// Case 1: Condition argument not provided
let matches = Command::new("example")
.arg(Arg::new("mode").long("mode"))
.arg(
Arg::new("value")
.long("value")
.default_value_if("mode", "fast", "100")
)
.get_matches_from(&["example"]);
// mode not provided -> value has no default (None unless unconditional default)
assert!(matches.get_one::<String>("value").is_none());
// Case 2: Condition argument provided but value doesn't match
let matches = Command::new("example")
.arg(Arg::new("mode").long("mode").default_value("slow"))
.arg(
Arg::new("value")
.long("value")
.default_value_if("mode", "fast", "100")
)
.get_matches_from(&["example"]);
// mode is "slow", not "fast" -> value has no default
assert!(matches.get_one::<String>("value").is_none());
// Case 3: Multiple conditions, some match
let matches = Command::new("example")
.arg(Arg::new("mode").long("mode").default_value("medium"))
.arg(
Arg::new("value")
.long("value")
.default_value_if("mode", "fast", "10")
.default_value_if("mode", "medium", "50")
.default_value_if("mode", "slow", "100")
)
.get_matches_from(&["example"]);
// First matching condition wins: "medium" matches second
assert_eq!(matches.get_one::<String>("value").unwrap(), "50");
}Understanding behavior when conditions don't match is crucial for correct usage.
Summary Table
fn summary_table() {
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β Pattern β Behavior β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
// β default_value_if("arg", "val", "x") β Default "x" if arg equals "val" β
// β default_value_if("arg", None, "x") β Default "x" if arg has any valueβ
// β Multiple conditions β First match wins β
// β + default_value("x") β Fallback if no conditions matchβ
// β User provides value β Always overrides default β
// β Condition arg not provided β No conditional default applied β
// β Condition value doesn't match β No conditional default applied β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
}Key Points Summary
fn key_points() {
// 1. default_value_if sets defaults based on other argument values
// 2. Format: default_value_if("arg_name", "expected_value", "default")
// 3. Use None as expected_value to match any value presence
// 4. Multiple conditions are evaluated in order; first match wins
// 5. Combine with default_value for unconditional fallback
// 6. User-provided values always override conditional defaults
// 7. No match = no default (argument may be None)
// 8. Works with ArgAction for flag presence detection
// 9. Integrates with value_parser for validation
// 10. Conditions can check presence or specific values
// 11. Useful for context-sensitive configuration
// 12. Reduces need for manual default resolution in code
}Key insight: default_value_if enables declarative conditional logic directly in argument definitions, eliminating boilerplate code that would otherwise check argument values and set defaults manually. The pattern scales well: a single configuration argument can influence multiple other arguments' defaults, and the conditions compose naturally with explicit user values taking precedence. This keeps the argument parsing logic in one placeβthe command definitionβrather than scattered through the application code. Use unconditional default_value as a fallback when no conditions match to ensure arguments always have sensible values.
