What is the difference between clap::Arg::required and required_if_eq for conditional argument validation?

required enforces unconditional argument presence, while required_if_eq makes an argument conditionally required based on another argument's value. With required(true), the argument must always be provided regardless of context. With required_if_eq("other_arg", "value"), the argument becomes required only when other_arg equals the specified value. This enables sophisticated command-line interfaces where argument requirements depend on user choices—like requiring --output only when --format is set to file, or requiring --port only when --bind-address is specified. These conditional requirements integrate with clap's help generation, automatically displaying context-aware usage messages that reflect which arguments are required under which conditions.

Unconditional Requirements with required

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("myapp")
        .arg(
            Arg::new("input")
                .long("input")
                .required(true)
                .help("Input file path"),
        )
        .arg(
            Arg::new("output")
                .long("output")
                .required(true)
                .help("Output file path"),
        )
        .get_matches();
 
    // Both --input and --output MUST be provided
    println!("Input: {:?}", matches.get_one::<String>("input"));
    println!("Output: {:?}", matches.get_one::<String>("output"));
}
 
// Running without arguments:
// $ myapp
// error: The following required arguments were not provided:
//     --input <input>
//     --output <output>

required(true) creates unconditional dependencies that must be satisfied regardless of other arguments.

Conditional Requirements with required_if_eq

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("myapp")
        .arg(
            Arg::new("format")
                .long("format")
                .value_parser(["json", "xml", "file"])
                .default_value("json"),
        )
        .arg(
            Arg::new("output")
                .long("output")
                .required_if_eq("format", "file")
                .help("Output file (required when format=file)"),
        )
        .get_matches();
 
    // --output is only required when --format=file
    // Otherwise it's optional
}
 
// Running with --format json:
// $ myapp --format json
// OK! No --output required
 
// Running with --format file:
// $ myapp --format file
// error: The following required arguments were not provided:
//     --output <output>

required_if_eq creates conditional requirements based on another argument's value.

Multiple Conditional Requirements

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("server")
        .arg(
            Arg::new("mode")
                .long("mode")
                .value_parser(["local", "remote"])
                .default_value("local"),
        )
        .arg(
            Arg::new("port")
                .long("port")
                .required_if_eq("mode", "remote")
                .help("Port number (required for remote mode)"),
        )
        .arg(
            Arg::new("host")
                .long("host")
                .required_if_eq("mode", "remote")
                .help("Host address (required for remote mode)"),
        )
        .arg(
            Arg::new("timeout")
                .long("timeout")
                .required_if_eq("mode", "remote")
                .help("Connection timeout (required for remote mode)"),
        )
        .get_matches();
 
    // Multiple arguments become required when mode=remote
}
 
// Local mode: only --mode (or default)
// $ server --mode local
// OK - no additional requirements
 
// Remote mode: --port, --host, --timeout all required
// $ server --mode remote
// error: The following required arguments were not provided:
//     --port <port>
//     --host <host>
//     --timeout <timeout>

Multiple arguments can depend on the same condition, creating groups of required arguments.

Combining Required and RequiredIfEq

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("database")
        // Always required
        .arg(
            Arg::new("name")
                .long("name")
                .required(true)
                .help("Database name"),
        )
        // Conditionally required
        .arg(
            Arg::new("host")
                .long("host")
                .required_if_eq("type", "remote")
                .help("Host address for remote databases"),
        )
        // Optional
        .arg(
            Arg::new("type")
                .long("type")
                .value_parser(["local", "remote"])
                .default_value("local"),
        )
        .get_matches();
 
    // --name is always required
    // --host is only required when --type=remote
}
 
// $ database
// error: --name is required
 
// $ database --name mydb --type local
// OK - --host not required
 
// $ database --name mydb --type remote
// error: --host is required when type=remote

Arguments can mix unconditional and conditional requirements.

RequiredIfEq with Custom Validation

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("compiler")
        .arg(
            Arg::new("input")
                .required(true)
                .help("Input source file"),
        )
        .arg(
            Arg::new("target")
                .long("target")
                .value_parser(["native", "wasm", "llvm"])
                .default_value("native"),
        )
        .arg(
            Arg::new("output")
                .long("output")
                .required_if_eq("target", "wasm")
                .required_if_eq("target", "llvm")
                .help("Output file (required for non-native targets)"),
        )
        .arg(
            Arg::new("optimize")
                .long("optimize")
                .required_if_eq("target", "llvm")
                .help("Optimization level (required for LLVM backend)"),
        )
        .get_matches();
 
    // Different targets have different requirements:
    // native: only input required
    // wasm: input + output required
    // llvm: input + output + optimize required
}
 
// $ compiler input.rs --target native
// OK
 
// $ compiler input.rs --target wasm
// error: --output is required
 
// $ compiler input.rs --target llvm
// error: --output and --optimize are required

The same argument can have multiple required_if_eq conditions with different values.

Semantics of Required vs RequiredIfEq

use clap::{Arg, Command};
 
fn main() {
    // required(true): Unconditional
    // - Argument must always be present
    // - No context dependency
    // - Simple validation
    // - Clear error messages
    
    // required_if_eq(arg, value): Conditional
    // - Argument required only when arg == value
    // - Context-dependent validation
    // - More complex error messaging
    // - Enables hierarchical argument structures
    
    let cmd = Command::new("example")
        .arg(
            Arg::new("always")
                .long("always")
                .required(true),  // ALWAYS required
        )
        .arg(
            Arg::new("conditional")
                .long("conditional")
                .required_if_eq("flag", "enabled"),  // Required only when flag=enabled
        )
        .arg(
            Arg::new("flag")
                .long("flag")
                .value_parser(["enabled", "disabled"])
                .default_value("disabled"),
        );
    
    // always: Must be provided
    // conditional: Only required when flag=enabled
}

The semantic difference is about unconditional vs conditional obligation.

RequiredIfEq with Positional Arguments

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("tool")
        .arg(
            Arg::new("command")
                .value_parser(["build", "run", "test"])
                .required(true)
                .index(1),
        )
        .arg(
            Arg::new("file")
                .required_if_eq("command", "build")
                .required_if_eq("command", "test")
                .index(2)
                .help("File argument (required for build and test)"),
        )
        .arg(
            Arg::new("args")
                .index(3)
                .help("Additional arguments"),
        )
        .get_matches();
 
    // Positional arguments can also use required_if_eq
    // build <file>: file required
    // run: no file required
    // test <file>: file required
}
 
// $ tool build
// error: <file> is required for build command
 
// $ tool run
// OK - no file required
 
// $ tool test src/main.rs
// OK

required_if_eq works with positional arguments using index-based references.

RequiredIfEqAll for Multiple Conditions

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("deploy")
        .arg(
            Arg::new("env")
                .long("env")
                .value_parser(["dev", "staging", "prod"]),
        )
        .arg(
            Arg::new("region")
                .long("region")
                .value_parser(["us-east", "us-west", "eu-west"]),
        )
        .arg(
            Arg::new("cert")
                .long("cert")
                .required_if_eq("env", "prod")  // Required for prod
                .help("SSL certificate path"),
        )
        .arg(
            Arg::new("backup")
                .long("backup")
                // Multiple conditions - required if ANY condition is met
                .required_if_eq("env", "prod")
                .required_if_eq("env", "staging")
                .help("Backup configuration"),
        )
        .get_matches();
 
    // --cert: required when env=prod
    // --backup: required when env=prod OR env=staging
}
 
// Note: Multiple required_if_eq calls create OR logic
// Required if ANY condition matches

Multiple required_if_eq calls on the same argument create OR conditions.

RequiredUnlessEq for Inverse Conditions

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("config")
        .arg(
            Arg::new("mode")
                .long("mode")
                .value_parser(["auto", "manual"])
                .default_value("auto"),
        )
        .arg(
            Arg::new("config-file")
                .long("config-file")
                .required_unless_eq("mode", "auto")
                .help("Configuration file (required unless mode=auto)"),
        )
        .arg(
            Arg::new("setting")
                .long("setting")
                .required_if_eq("mode", "manual")
                .help("Manual setting (required when mode=manual)"),
        )
        .get_matches();
 
    // required_if_eq("mode", "manual"): Required when mode=manual
    // required_unless_eq("mode", "auto"): Required when mode!=auto
    // These are inverse conditions
}
 
// required_if_eq(A, V): Required when arg A equals value V
// required_unless_eq(A, V): Required when arg A does NOT equal value V

required_unless_eq provides the inverse semantic—require unless a condition is met.

Conditional Requirement Chains

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("workflow")
        // Level 1: Base option
        .arg(
            Arg::new("action")
                .long("action")
                .value_parser(["create", "update", "delete"])
                .default_value("create"),
        )
        // Level 2: Required for specific actions
        .arg(
            Arg::new("id")
                .long("id")
                .required_if_eq("action", "update")
                .required_if_eq("action", "delete")
                .help("Resource ID (required for update/delete)"),
        )
        // Level 3: Required for update action
        .arg(
            Arg::new("name")
                .long("name")
                .required_if_eq("action", "update")
                .help("New name (required for update)"),
        )
        // Level 3: Required for create action
        .arg(
            Arg::new("template")
                .long("template")
                .required_if_eq("action", "create")
                .help("Template name (required for create)"),
        )
        .get_matches();
 
    // Creates a tree of conditional requirements:
    // create -> requires template
    // update -> requires id + name
    // delete -> requires id
}
 
// Different actions have different required argument sets
// This creates command-like behavior with conditional requirements

Conditional requirements can create dependency trees for complex CLI structures.

Error Messages and Help Generation

use clap::{Arg, Command};
 
fn main() {
    let cmd = Command::new("example")
        .arg(
            Arg::new("format")
                .long("format")
                .value_parser(["json", "yaml", "file"]),
        )
        .arg(
            Arg::new("output")
                .long("output")
                .required_if_eq("format", "file")
                .help("Output path (required when --format=file)"),
        );
    
    // Help text automatically reflects conditional requirements
    // clap generates usage that shows when arguments are required
    
    // Running --format file without --output:
    // error: The following required arguments were not provided:
    //     --output <output>
    //
    // USAGE:
    //     example --format <format> --output <output>
    //
    // The usage string adapts to show required arguments
}

Clap's help generation adapts to show conditional requirements in usage messages.

Required Groups with Conditional Logic

use clap::{Arg, ArgGroup, Command};
 
fn main() {
    let matches = Command::new("remote")
        .arg(
            Arg::new("ssh")
                .long("ssh")
                .help("Use SSH connection"),
        )
        .arg(
            Arg::new("https")
                .long("https")
                .help("Use HTTPS connection"),
        )
        .arg(
            Arg::new("host")
                .long("host")
                .required(true)
                .help("Remote host address"),
        )
        .arg(
            Arg::new("key")
                .long("key")
                .required_if_eq("ssh", "true")
                .help("SSH key path (required when using SSH)"),
        )
        .arg(
            Arg::new("cert")
                .long("cert")
                .required_if_eq("https", "true")
                .help("Certificate path (required when using HTTPS)"),
        )
        .group(
            ArgGroup::new("protocol")
                .args(["ssh", "https"])
                .required(true),
        )
        .get_matches();
 
    // Protocol group is required (ssh OR https)
    // --key required when --ssh is used
    // --cert required when --https is used
}
 
// Creates mutually exclusive options with conditional dependencies

Combining ArgGroup with required_if_eq creates sophisticated validation rules.

Runtime Validation Patterns

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("processor")
        .arg(
            Arg::new("mode")
                .long("mode")
                .value_parser(["encode", "decode"])
                .default_value("encode"),
        )
        .arg(
            Arg::new("input")
                .required(true)
                .help("Input file"),
        )
        .arg(
            Arg::new("key")
                .long("key")
                .required_if_eq("mode", "encode")
                .required_if_eq("mode", "decode")
                .help("Encryption key (required for both modes)"),
        )
        .arg(
            Arg::new("output")
                .long("output")
                .required_if_eq("mode", "encode")
                .help("Output file (required for encode)"),
        )
        .arg(
            Arg::new("verify")
                .long("verify")
                .required_if_eq("mode", "decode")
                .help("Verify integrity (required for decode)"),
        )
        .get_matches();
 
    // Different modes require different argument sets:
    // encode: input + key + output
    // decode: input + key + verify
    
    let mode = matches.get_one::<String>("mode").unwrap();
    match mode.as_str() {
        "encode" => {
            let input = matches.get_one::<String>("input").unwrap();
            let key = matches.get_one::<String>("key").unwrap();
            let output = matches.get_one::<String>("output").unwrap();
            // Encode logic
        }
        "decode" => {
            let input = matches.get_one::<String>("input").unwrap();
            let key = matches.get_one::<String>("key").unwrap();
            let verify = matches.get_one::<String>("verify").unwrap();
            // Decode logic
        }
        _ => unreachable!(),
    }
}

Runtime code can assume conditional requirements are satisfied when they're required.

Practical Example: Git-like CLI

use clap::{Arg, Command};
 
fn main() {
    let matches = Command::new("git-like")
        .subcommand_required(true)
        .subcommand(
            Command::new("commit")
                .arg(
                    Arg::new("message")
                        .short('m')
                        .long("message")
                        .required(true)
                        .help("Commit message"),
                )
                .arg(
                    Arg::new("amend")
                        .long("amend")
                        .help("Amend previous commit"),
                )
                .arg(
                    Arg::new("author")
                        .long("author")
                        .required_if_eq("amend", "true")
                        .help("Author name (required when amending)"),
                ),
        )
        .subcommand(
            Command::new("push")
                .arg(
                    Arg::new("remote")
                        .required(true)
                        .help("Remote name"),
                )
                .arg(
                    Arg::new("branch")
                        .required(true)
                        .help("Branch name"),
                )
                .arg(
                    Arg::new("force")
                        .long("force")
                        .help("Force push"),
                )
                .arg(
                    Arg::new("lease")
                        .long("lease")
                        .required_if_eq("force", "true")
                        .help("Lease token (required when force pushing)"),
                ),
        )
        .get_matches();
 
    match matches.subcommand() {
        Some(("commit", sub_matches)) => {
            let message = sub_matches.get_one::<String>("message").unwrap();
            let amend = sub_matches.contains_id("amend");
            let author = sub_matches.get_one::<String>("author");
            
            if amend {
                // author is guaranteed to be Some when amend is true
                println!("Amending with author: {:?}", author);
            }
            println!("Commit: {}", message);
        }
        Some(("push", sub_matches)) => {
            let remote = sub_matches.get_one::<String>("remote").unwrap();
            let branch = sub_matches.get_one::<String>("branch").unwrap();
            let force = sub_matches.contains_id("force");
            let lease = sub_matches.get_one::<String>("lease");
            
            if force {
                // lease is guaranteed to be Some when force is true
                println!("Force push with lease: {:?}", lease);
            }
            println!("Push {} to {}", branch, remote);
        }
        _ => unreachable!(),
    }
}
 
// Each subcommand has its own conditional requirements
// --author required when --amend is present
// --lease required when --force is present

Subcommands can have their own conditional requirement chains.

Synthesis

Key differences:

Aspect required(true) required_if_eq(arg, value)
Obligation Always required Conditionally required
Context No context needed Depends on another arg
Error message "required argument not provided" "required when X=Y"
Use case Global requirements Mode-dependent requirements

When to use each:

// Use required(true) when:
// - Argument is always needed regardless of context
// - No dependencies on other arguments
// - Simple, unconditional validation
 
.arg(Arg::new("input").required(true))
 
// Use required_if_eq when:
// - Argument requirement depends on user choices
// - Different modes need different arguments
// - Building command-like subcommand structures
 
.arg(Arg::new("output").required_if_eq("format", "file"))
 
// Combine both:
.arg(Arg::new("name").required(true))  // Always required
.arg(Arg::new("port").required_if_eq("mode", "server"))  // Conditionally required

Multiple conditions:

// OR logic: required if ANY condition matches
.arg(Arg::new("backup")
    .required_if_eq("env", "prod")
    .required_if_eq("env", "staging"))
 
// AND logic: use required_if_eq_all (if available)
// or nest conditions in your validation logic

Key insight: required(true) declares unconditional argument necessity, while required_if_eq(arg, value) creates conditional dependencies that adapt to user input. This enables sophisticated CLI designs where argument requirements reflect semantic relationships between options—different operational modes can require different argument sets, and the help system automatically communicates these requirements. The validation happens at parse time, producing clear error messages that explain not just that an argument is missing, but the condition under which it's required. For complex CLIs, combining subcommands with conditional requirements creates powerful, user-friendly interfaces that guide users toward correct invocation without overwhelming them with requirements that don't apply to their use case.