What is the purpose of clap::ArgGroup for defining mutually exclusive or related arguments?
clap::ArgGroup defines groups of arguments that are related in some wayβeither mutually exclusive (only one can be present), collectively required (at least one must be present), or simply grouped for display purposes in help text. Groups enable validation rules that span multiple arguments and clarify relationships between options without requiring custom validation code.
Understanding Argument Groups
use clap::{Arg, ArgGroup, Command};
fn basic_group() {
// ArgGroup collects related arguments under a single name
// Groups can express:
// - Mutual exclusivity (only one allowed)
// - Required group (at least one required)
// - Display grouping (help organization)
let matches = Command::new("myapp")
.arg(Arg::new("verbose")
.short('v')
.long("verbose"))
.arg(Arg::new("quiet")
.short('q')
.long("quiet"))
.group(ArgGroup::new("verbosity")
.args(["verbose", "quiet"]))
.get_matches_from(vec!["myapp", "-v"]);
}ArgGroup creates a named collection of arguments with validation rules.
Creating Mutually Exclusive Arguments
use clap::{Arg, ArgGroup, Command};
fn mutually_exclusive() {
// Mutually exclusive: only one argument in group can be used
let matches = 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("xml")
.long("xml")
.help("Output as XML"))
// Group makes these mutually exclusive
.group(ArgGroup::new("format")
.args(["json", "yaml", "xml"])
.required(false)) // None required, but if provided, only one
.get_matches_from(vec!["myapp", "--json"]);
// Valid: one format specified
assert!(matches.contains_id("json"));
assert!(!matches.contains_id("yaml"));
assert!(!matches.contains_id("xml"));
}
fn mutually_exclusive_error() {
// Using multiple arguments from the group fails
let result = Command::new("myapp")
.arg(Arg::new("json").long("json"))
.arg(Arg::new("yaml").long("yaml"))
.group(ArgGroup::new("format")
.args(["json", "yaml"])
.required(false))
.try_get_matches_from(vec!["myapp", "--json", "--yaml"]);
// Error: argument '--yaml' cannot be used with '--json'
assert!(result.is_err());
}Groups make arguments mutually exclusiveβonly one can be used at a time.
Required Groups: At Least One Required
use clap::{Arg, ArgGroup, Command};
fn required_group() {
// Required group: at least one argument must be present
let matches = Command::new("myapp")
.arg(Arg::new("input")
.long("input")
.value_name("FILE"))
.arg(Arg::new("stdin")
.long("stdin"))
.group(ArgGroup::new("source")
.args(["input", "stdin"])
.required(true)) // At least one must be present
.get_matches_from(vec!["myapp", "--input", "data.txt"]);
// One of the group members is present
assert!(matches.contains_id("input"));
}
fn required_group_error() {
// Missing all arguments from required group fails
let result = Command::new("myapp")
.arg(Arg::new("input").long("input"))
.arg(Arg::new("stdin").long("stdin"))
.group(ArgGroup::new("source")
.args(["input", "stdin"])
.required(true))
.try_get_matches_from(vec!["myapp"]);
// Error: The following required arguments were not provided:
// <--input|--stdin>
assert!(result.is_err());
}Setting .required(true) on a group requires at least one argument from the group.
Combining Required and Mutually Exclusive
use clap::{Arg, ArgGroup, Command};
fn required_and_exclusive() {
// A group can be both required and mutually exclusive
// Meaning: exactly one must be present
let matches = Command::new("converter")
.arg(Arg::new("to-json")
.long("to-json"))
.arg(Arg::new("to-yaml")
.long("to-yaml"))
.arg(Arg::new("to-toml")
.long("to-toml"))
.group(ArgGroup::new("output-format")
.args(["to-json", "to-yaml", "to-toml"])
.required(true)) // One required, only one allowed
.get_matches_from(vec!["converter", "--to-json"]);
// Exactly one format is present
assert!(matches.contains_id("to-json"));
// Can determine which one:
if matches.contains_id("to-json") {
println!("Converting to JSON");
} else if matches.contains_id("to-yaml") {
println!("Converting to YAML");
} else if matches.contains_id("to-toml") {
println!("Converting to TOML");
}
}.required(true) on a group with multiple arguments means "exactly one" (required + mutually exclusive).
Group Display in Help Text
use clap::{Arg, ArgGroup, Command};
fn help_display() {
// Groups improve help text organization
let cmd = Command::new("myapp")
.about("Data processor")
.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(ArgGroup::new("format")
.args(["json", "yaml", "toml"])
.required(false));
// Help text shows:
// Options:
// --json Output as JSON
// --yaml Output as YAML
// --toml Output as TOML
//
// Or with group name shown:
// Format:
// --json Output as JSON
// --yaml Output as YAML
// --toml Output as TOML
}Groups can improve help text organization when named appropriately.
Getting Values from Groups
use clap::{Arg, ArgGroup, Command};
fn getting_group_values() {
// Get which argument from the group was used
let matches = Command::new("myapp")
.arg(Arg::new("json").long("json"))
.arg(Arg::new("yaml").long("yaml"))
.group(ArgGroup::new("format")
.args(["json", "yaml"]))
.get_matches_from(vec!["myapp", "--yaml"]);
// Method 1: Check each argument
if matches.contains_id("json") {
println!("JSON format selected");
} else if matches.contains_id("yaml") {
println!("YAML format selected");
}
// Method 2: Get the argument ID that was present
if let Some(arg_id) = matches.get_one::<String>("format") {
// Returns the ID of the argument that was used
println!("Format group: {}", arg_id); // Prints "yaml"
}
}The group name can be used to get which argument from the group was provided.
Multiple Values in Groups
use clap::{Arg, ArgGroup, Command};
fn values_in_groups() {
// Arguments in groups can have values
let matches = Command::new("myapp")
.arg(Arg::new("config")
.long("config")
.value_name("FILE"))
.arg(Arg::new("env")
.long("env")
.value_name("VAR"))
.group(ArgGroup::new("config-source")
.args(["config", "env"])
.required(true))
.get_matches_from(vec!["myapp", "--config", "settings.toml"]);
// Get the value from whichever argument was used
if let Some(config_path) = matches.get_one::<String>("config") {
println!("Config file: {}", config_path);
} else if let Some(env_var) = matches.get_one::<String>("env") {
println!("Environment variable: {}", env_var);
}
}Arguments in groups can accept values just like standalone arguments.
Nested Groups and Multiple Groups
use clap::{Arg, ArgGroup, Command};
fn multiple_groups() {
// Arguments can belong to multiple groups
let matches = Command::new("myapp")
.arg(Arg::new("verbose")
.short('v')
.long("verbose"))
.arg(Arg::new("quiet")
.short('q')
.long("quiet"))
.arg(Arg::new("debug")
.short('d')
.long("debug"))
.arg(Arg::new("silent")
.short('s')
.long("silent"))
// Group 1: verbosity level (mutually exclusive)
.group(ArgGroup::new("verbosity")
.args(["verbose", "quiet"])
.required(false))
// Group 2: all output-related flags
.group(ArgGroup::new("output")
.args(["verbose", "quiet", "debug", "silent"])
.required(false))
.get_matches_from(vec!["myapp", "-v", "-d"]);
// verbose and debug can coexist (different groups)
assert!(matches.contains_id("verbose"));
assert!(matches.contains_id("debug"));
}Arguments can belong to multiple groups with different validation rules.
Using Group Names for Logic
use clap::{Arg, ArgGroup, Command};
fn group_based_logic() {
// Use group membership for cleaner logic
let matches = Command::new("myapp")
.arg(Arg::new("install")
.long("install")
.conflicts_with("uninstall"))
.arg(Arg::new("uninstall")
.long("uninstall")
.conflicts_with("install"))
.arg(Arg::new("update")
.long("update")
.conflicts_with("install"))
.group(ArgGroup::new("operation")
.args(["install", "uninstall", "update"])
.required(true))
.get_matches_from(vec!["myapp", "--install"]);
// Determine operation from group
match matches.get_one::<String>("operation").map(|s| s.as_str()) {
Some("install") => println!("Installing..."),
Some("uninstall") => println!("Uninstalling..."),
Some("update") => println!("Updating..."),
_ => unreachable!("group is required"),
}
}The group name returns which argument from the group was used.
Conflicts with Individual Arguments
use clap::{Arg, ArgGroup, Command};
fn group_conflicts() {
// Groups can conflict with individual arguments
let matches = Command::new("myapp")
.arg(Arg::new("stdin")
.long("stdin"))
.arg(Arg::new("file")
.long("file"))
.arg(Arg::new("output")
.long("output"))
.group(ArgGroup::new("input")
.args(["stdin", "file"])
.required(true))
.arg(Arg::new("quiet")
.long("quiet")
.conflicts_with("stdin")) // quiet conflicts with stdin
.get_matches_from(vec!["myapp", "--file", "data.txt", "--quiet"]);
// Valid: quiet conflicts with stdin, but we used file
assert!(matches.contains_id("file"));
assert!(matches.contains_id("quiet"));
}
fn group_conflict_error() {
let result = Command::new("myapp")
.arg(Arg::new("stdin").long("stdin"))
.arg(Arg::new("file").long("file"))
.group(ArgGroup::new("input")
.args(["stdin", "file"])
.required(true))
.arg(Arg::new("quiet")
.long("quiet")
.conflicts_with("stdin"))
.try_get_matches_from(vec!["myapp", "--stdin", "--quiet"]);
// Error: quiet conflicts with stdin
assert!(result.is_err());
}Individual arguments can have conflicts, even when part of a group.
Subcommand Groups
use clap::{Arg, ArgGroup, Command};
fn subcommand_groups() {
// Groups work in subcommands too
let matches = Command::new("myapp")
.subcommand(Command::new("build")
.arg(Arg::new("release")
.long("release"))
.arg(Arg::new("debug")
.long("debug"))
.group(ArgGroup::new("mode")
.args(["release", "debug"])
.required(false)))
.get_matches_from(vec!["myapp", "build", "--release"]);
if let Some(sub_matches) = matches.subcommand_matches("build") {
if sub_matches.contains_id("release") {
println!("Building in release mode");
}
}
}Groups work within subcommands for hierarchical command structures.
Complete Summary
use clap::{Arg, ArgGroup, Command};
fn complete_summary() {
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β ArgGroup Property β Behavior β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
// β .args([...]) β Defines which arguments belong to group β
// β .required(true) β At least one argument must be present β
// β .required(false) β All arguments optional, mutually exclusive β
// β Multiple args + req β Exactly one must be present β
// β .arg("name") β Adds single argument to group β
// β .multiple(true) β Allows multiple from group (not mutually excl) β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Common patterns:
// Pattern 1: Mutually exclusive (zero or one)
// .group(ArgGroup::new("format")
// .args(["json", "yaml", "xml"])
// .required(false))
// Pattern 2: Required, mutually exclusive (exactly one)
// .group(ArgGroup::new("operation")
// .args(["install", "uninstall", "update"])
// .required(true))
// Pattern 3: Required, multiple allowed
// .group(ArgGroup::new("files")
// .args(["input", "output"])
// .required(true)
// .multiple(true))
}
// Key insight:
// ArgGroup serves three purposes: validation, help organization,
// and value retrieval. For validation, it enforces mutual exclusivity
// (only one of the group can be used) and/or required status (at least
// one must be used). For help, it organizes related options together.
// For retrieval, the group name acts as a key to get which argument
// was provided. The key distinction is:
//
// - required(false) + multiple args = zero or one (optional, exclusive)
// - required(true) + multiple args = exactly one (required + exclusive)
// - required(true) + multiple(true) = at least one, multiple allowed
//
// Groups eliminate the need for manual validation code like:
// "if json and yaml both present, error" - clap handles it automatically.
// Groups also enable cleaner match statements using the group name
// instead of checking each argument individually.Key insight: ArgGroup provides three main benefits: (1) Mutual exclusivity - arguments in a group can only have one present at a time, (2) Required validation - setting .required(true) ensures at least one argument from the group is provided, and (3) Cleaner logic - the group name acts as a key to retrieve which argument was used. The combination of required(true) with multiple arguments means "exactly one must be present" (required + mutually exclusive). This eliminates manual validation code for checking incompatible arguments or missing required options, and enables cleaner match expressions using the group name rather than checking each argument individually.
