Loading page…
Rust walkthroughs
Loading page…
clap::Arg::number_of_values, how does it validate argument counts at parse time?clap::Arg::number_of_values specifies how many values an argument expects, and validation occurs during the parsing phase when command-line arguments are processed. If the number of provided values doesn't match the specification, clap reports an error before the application can access the parsed values. The method accepts either a fixed count (e.g., number_of_values(3) for exactly 3 values), a range (e.g., 1..=3 for 1 to 3 values), or uses special delimiters to separate multiple values. The validation is immediate and strict—parsing fails with a descriptive error message rather than silently truncating or accepting invalid input.
use clap::{Arg, Command};
fn basic_usage() {
let matches = Command::new("myapp")
.arg(
Arg::new("files")
.long("files")
.number_of_values(3)
.required(true)
)
.try_get_matches_from(vec!["myapp", "--files", "a.txt", "b.txt", "c.txt"]);
match matches {
Ok(m) => {
let files: Vec<&str> = m.get_many("files").unwrap().collect();
println!("Files: {:?}", files); // ["a.txt", "b.txt", "c.txt"]
}
Err(e) => {
println!("Error: {}", e);
}
}
}With number_of_values(3), exactly 3 values must follow the argument.
use clap::{Arg, Command};
fn validation_failures() {
// Too few values
let result = Command::new("myapp")
.arg(
Arg::new("files")
.long("files")
.number_of_values(3)
)
.try_get_matches_from(vec!["myapp", "--files", "a.txt", "b.txt"]);
// Error: The argument '--files <files>' requires 3 values, but 2 were provided
// Too many values
let result = Command::new("myapp")
.arg(
Arg::new("files")
.long("files")
.number_of_values(3)
)
.try_get_matches_from(vec!["myapp", "--files", "a.txt", "b.txt", "c.txt", "d.txt"]);
// Error: The argument '--files <files>' requires 3 values, but 4 were provided
// Correct count
let result = Command::new("myapp")
.arg(
Arg::new("files")
.long("files")
.number_of_values(3)
)
.try_get_matches_from(vec!["myapp", "--files", "a.txt", "b.txt", "c.txt"]);
// Success!
}Clap validates value count immediately during parsing.
use clap::{Arg, Command};
fn fixed_count() {
let matches = Command::new("coords")
.arg(
Arg::new("position")
.long("position")
.number_of_values(2) // Exactly 2 values
.value_names(["X", "Y"])
)
.get_matches_from(vec!["coords", "--position", "10", "20"]);
let coords: Vec<&str> = matches.get_many("position").unwrap().collect();
println!("Position: x={}, y={}", coords[0], coords[1]);
// Position: x=10, y=20
// If you provide 1 or 3 values, parsing fails
// "--position 10" -> error
// "--position 10 20 30" -> error
}Fixed count requires exactly that many values.
use clap::{Arg, Command};
fn range_of_values() {
// Accept 1 to 3 files
let matches = Command::new("myapp")
.arg(
Arg::new("files")
.long("files")
.number_of_values(1..=3) // 1, 2, or 3 values
)
.get_matches_from(vec!["myapp", "--files", "a.txt", "b.txt"]);
let files: Vec<&str> = matches.get_many("files").unwrap().collect();
println!("Files: {:?}", files); // ["a.txt", "b.txt"]
// All of these are valid:
// --files a.txt (1 value) ✓
// --files a.txt b.txt (2 values) ✓
// --files a.txt b.txt c.txt (3 values) ✓
// This fails:
// --files a.txt b.txt c.txt d.txt (4 values) ✗
// Using exclusive range (0 to 2, meaning 0, 1)
let cmd = Command::new("myapp")
.arg(
Arg::new("optional")
.long("optional")
.number_of_values(0..2) // 0 or 1 values
);
}Ranges allow flexible value counts within bounds.
use clap::{Arg, Command};
fn zero_values() {
// number_of_values(0) means no values accepted
let cmd = Command::new("myapp")
.arg(
Arg::new("verbose")
.long("verbose")
.number_of_values(0) // Flag with no value
);
// This is similar to a simple flag (.action(ArgAction::SetTrue))
// but allows explicit specification of "no values expected"
// Using it with a value causes an error:
// myapp --verbose true -> Error
}number_of_values(0) means the argument accepts no values.
use clap::{Arg, Command};
fn value_separation() {
// By default, values are space-separated
let matches = Command::new("myapp")
.arg(
Arg::new("coords")
.long("coords")
.number_of_values(2)
)
.get_matches_from(vec!["myapp", "--coords", "10", "20"]);
// Values: ["10", "20"]
// With value_delimiter, values can be comma-separated
let matches = Command::new("myapp")
.arg(
Arg::new("coords")
.long("coords")
.number_of_values(2)
.value_delimiter(',')
)
.get_matches_from(vec!["myapp", "--coords", "10,20"]);
// Values: ["10", "20"] (comma splits into 2 values)
// Both forms work with value_delimiter
// --coords 10,20 -> splits to ["10", "20"]
// --coords 10 20 -> ["10", "20"] (2 space-separated values)
}value_delimiter allows splitting a single argument string into multiple values.
use clap::{Arg, Command, ArgAction};
fn occurrences_vs_values() {
// Multiple VALUES per occurrence
let matches = Command::new("myapp")
.arg(
Arg::new("coords")
.long("coords")
.number_of_values(2)
)
.get_matches_from(vec!["myapp", "--coords", "10", "20"]);
// One occurrence, two values: ["10", "20"]
// Multiple OCCURRENCES, one value each
let matches = Command::new("myapp")
.arg(
Arg::new("file")
.long("file")
.action(ArgAction::Append) // Allow multiple occurrences
)
.get_matches_from(vec!["myapp", "--file", "a.txt", "--file", "b.txt"]);
// Two occurrences: ["a.txt", "b.txt"]
// Combining both: multiple occurrences with multiple values each
let matches = Command::new("myapp")
.arg(
Arg::new("range")
.long("range")
.number_of_values(2)
.action(ArgAction::Append)
)
.get_matches_from(vec![
"myapp",
"--range", "1", "10",
"--range", "20", "30"
]);
// Two occurrences, each with 2 values
}Multiple occurrences and multiple values serve different purposes.
use clap::{Arg, Command, ArgAction};
fn multiple_occurrences_validation() {
// Each occurrence must have exactly 2 values
let result = Command::new("myapp")
.arg(
Arg::new("range")
.long("range")
.number_of_values(2)
.action(ArgAction::Append)
)
.try_get_matches_from(vec![
"myapp",
"--range", "1", "10",
"--range", "20", "30"
]);
// Success! Each occurrence has 2 values
// First occurrence has wrong count
let result = Command::new("myapp")
.arg(
Arg::new("range")
.long("range")
.number_of_values(2)
.action(ArgAction::Append)
)
.try_get_matches_from(vec![
"myapp",
"--range", "1", // Only 1 value for this occurrence
"--range", "20", "30"
]);
// Error: The argument '--range <range>' requires 2 values, but 1 was provided
}number_of_values validates each occurrence independently when using ArgAction::Append.
use clap::{Arg, Command};
fn positional_args() {
let matches = Command::new("myapp")
.arg(
Arg::new("files")
.number_of_values(2) // Exactly 2 positional args
.index(1)
)
.get_matches_from(vec!["myapp", "file1.txt", "file2.txt"]);
let files: Vec<&str> = matches.get_many("files").unwrap().collect();
println!("Files: {:?}", files); // ["file1.txt", "file2.txt"]
// With wrong count:
// myapp file1.txt -> Error (1 value, expected 2)
// myapp file1.txt file2.txt file3.txt -> Error (3 values, expected 2)
}number_of_values also works for positional arguments.
use clap::{Arg, Command};
fn combined_validation() {
let matches = Command::new("myapp")
.arg(
Arg::new("ports")
.long("ports")
.number_of_values(3)
.value_parser(clap::value_parser!(u16)) // Also validates type
)
.get_matches_from(vec!["myapp", "--ports", "80", "443", "8080"]);
let ports: Vec<u16> = matches.get_many::<u16>("ports").unwrap().copied().collect();
println!("Ports: {:?}", ports); // [80, 443, 8080]
// Type validation failure:
// --ports 80 abc 443 -> Error: "abc" is not a valid u16
// Count validation failure:
// --ports 80 443 -> Error: requires 3 values, got 2
}Count validation happens before type parsing for each value.
use clap::{Arg, Command, error::ErrorKind};
fn error_handling() {
let result = Command::new("myapp")
.arg(
Arg::new("files")
.long("files")
.number_of_values(3)
.required(true)
)
.try_get_matches_from(vec!["myapp", "--files", "a.txt"]);
match result {
Err(e) => {
println!("Error kind: {:?}", e.kind());
// ErrorKind::WrongNumberOfValues
println!("Error message: {}", e);
// error: The argument '--files <files>' requires 3 values, but 1 was provided
}
Ok(_) => println!("Success"),
}
}The error includes helpful context about expected vs. actual count.
use clap::{Arg, Command};
fn practical_examples() {
// Point coordinates (x, y)
let cmd = Command::new("draw")
.arg(
Arg::new("point")
.long("point")
.number_of_values(2)
.value_names(["x", "y"])
);
// Color with RGB values
let cmd = Command::new("image")
.arg(
Arg::new("color")
.long("color")
.number_of_values(3)
.value_names(["R", "G", "B"])
.value_parser(clap::value_parser!(u8))
);
// Date range (start and end)
let cmd = Command::new("report")
.arg(
Arg::new("range")
.long("date-range")
.number_of_values(2)
.value_names(["START", "END"])
);
// Accept 0 or more values (unbounded)
let cmd = Command::new("copy")
.arg(
Arg::new("sources")
.long("source")
.number_of_values(1..) // 1 or more (unbounded)
.action(clap::ArgAction::Append)
);
// Fixed set of values
let cmd = Command::new("transform")
.arg(
Arg::new("matrix")
.long("matrix")
.number_of_values(9) // 3x3 matrix as 9 values
);
}Common patterns use specific value counts for structured input.
use clap::{Arg, Command};
fn debug_parsing() {
// Enable debug output to see how clap parses values
std::env::set_var("CLAP_DEBUG", "1");
let matches = Command::new("myapp")
.arg(
Arg::new("files")
.long("files")
.number_of_values(2)
)
.get_matches_from(vec!["myapp", "--files", "a.txt", "b.txt"]);
// With CLAP_DEBUG=1, clap prints detailed parsing information
// showing how it groups values for each argument
}Debug mode helps understand how clap interprets argument structures.
| Specification | Meaning |
|---------------|---------|
| number_of_values(0) | No values allowed (flag-like) |
| number_of_values(1) | Exactly one value |
| number_of_values(3) | Exactly three values |
| number_of_values(1..=3) | One to three values (inclusive range) |
| number_of_values(0..2) | Zero or one value |
| number_of_values(1..) | One or more values (unbounded) |
clap::Arg::number_of_values provides strict validation during the parsing phase:
Validation timing: Count validation happens during get_matches/try_get_matches, before any application code can access the values. If the count is wrong, parsing fails with a descriptive error.
Per-occurrence validation: When combined with ArgAction::Append, each occurrence is validated independently. Each --arg val1 val2 must satisfy the count requirement.
Value separation: By default, values are space-separated on the command line. Use value_delimiter to allow comma-separated or other delimited values within a single argument string.
Error behavior: Wrong value count produces ErrorKind::WrongNumberOfValues with a message showing expected and actual counts. This prevents silent truncation or misinterpretation.
Key insight: number_of_values enforces a contract between your application and the command-line interface. If you need 2 coordinate values, specifying number_of_values(2) guarantees that when get_matches returns successfully, exactly 2 values exist—no defensive coding needed. The validation is front-loaded during parsing, keeping application logic clean and focused on the happy path. This is more robust than validating counts manually after parsing, because invalid input never reaches your application code.