How does clap::Arg::value_parser validate and parse command-line argument values?
clap::Arg::value_parser attaches a parser to an argument that transforms raw string input into typed values, validates the input against constraints, and generates helpful error messages for invalid values. It replaces the older validator and possible_values APIs with a unified system that handles both parsing and validation in a single step, producing strongly typed values while providing automatic shell completion support.
Basic value_parser Usage
use clap::{Arg, Command};
fn basic_usage() -> Command {
Command::new("myapp")
.arg(
Arg::new("count")
.long("count")
.value_parser(clap::value_parser!(i32))
)
}value_parser!(i32) creates a parser that converts the string argument to an i32, automatically rejecting non-integer inputs.
Built-in Value Parsers
use clap::{Arg, Command, value_parser};
fn builtin_parsers() -> Command {
Command::new("myapp")
.arg(
Arg::new("number")
.long("number")
.value_parser(value_parser!(i32))
.help("Parse as i32")
)
.arg(
Arg::new("count")
.long("count")
.value_parser(value_parser!(u64))
.help("Parse as u64")
)
.arg(
Arg::new("ratio")
.long("ratio")
.value_parser(value_parser!(f64))
.help("Parse as f64")
)
.arg(
Arg::new("name")
.long("name")
.value_parser(value_parser!(String))
.help("Parse as String (default for non-typed args)")
)
.arg(
Arg::new("verbose")
.long("verbose")
.value_parser(value_parser!(bool))
.help("Parse as bool")
)
.arg(
Arg::new("path")
.long("path")
.value_parser(value_parser!(std::path::PathBuf))
.help("Parse as PathBuf")
)
}value_parser! supports primitive types, String, OsString, and PathBuf out of the box.
How value_parser! Macro Works
use clap::value_parser;
fn macro_expansion() {
// value_parser!(i32) creates a TypedValueParser:
let parser = value_parser!(i32);
// Equivalent to:
// clap::builder::TypedValueParser::<i32>::new()
// The macro creates appropriate parser based on type:
// - Primitive types: RangedI64ValueParser, etc.
// - PathBuf: PathBufValueParser
// - String: StringValueParser
// - Custom types: can implement ValueParserFactory
}The macro dispatches to the appropriate parser implementation based on the type.
Parsing Results
use clap::{Arg, Command, value_parser};
fn parsing_results() {
let matches = Command::new("myapp")
.arg(
Arg::new("count")
.long("count")
.value_parser(value_parser!(i32))
.required(true)
)
.try_get_matches_from(["myapp", "--count", "42"]);
match matches {
Ok(matches) => {
// Get as typed value:
let count: i32 = *matches.get_one::<i32>("count").unwrap();
println!("Count: {}", count);
}
Err(e) => {
// Error handling includes validation errors:
println!("Error: {}", e);
}
}
}Parsed values are retrieved using get_one::<T>() which returns Option<&T>.
Automatic Error Messages
use clap::{Arg, Command, value_parser};
fn error_messages() {
let result = Command::new("myapp")
.arg(
Arg::new("count")
.long("count")
.value_parser(value_parser!(i32))
)
.try_get_matches_from(["myapp", "--count", "not_a_number"]);
match result {
Err(e) => {
// clap automatically generates helpful errors:
// error: Invalid value "not_a_number" for "--count":
// "not_a_number" is not a valid i32
println!("{}", e);
}
Ok(_) => unreachable!(),
}
let result = Command::new("myapp")
.arg(
Arg::new("count")
.long("count")
.value_parser(value_parser!(u32))
)
.try_get_matches_from(["myapp", "--count", "-5"]);
match result {
Err(e) => {
// Error: -5 is not a valid u32 (negative not allowed)
println!("{}", e);
}
Ok(_) => unreachable!(),
}
}value_parser generates descriptive error messages for invalid input.
Possible Values (Enum Validation)
use clap::{Arg, Command, builder::PossibleValuesParser, value_parser};
fn possible_values() -> Command {
Command::new("myapp")
.arg(
Arg::new("color")
.long("color")
.value_parser(["always", "auto", "never"])
.help("When to use colors")
)
}String slices create a PossibleValuesParser that accepts only the specified values.
Enum Parsing with Possible Values
use clap::{Arg, Command, value_parser};
use std::str::FromStr;
#[derive(Debug, Clone, Copy)]
enum Color {
Always,
Auto,
Never,
}
impl FromStr for Color {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"always" => Ok(Color::Always),
"auto" => Ok(Color::Auto),
"never" => Ok(Color::Never),
_ => Err(format!("invalid color: {}", s)),
}
}
}
fn enum_parsing() -> Command {
Command::new("myapp")
.arg(
Arg::new("color")
.long("color")
.value_parser(value_parser!(Color))
)
}
fn use_enum() {
let matches = Command::new("myapp")
.arg(
Arg::new("color")
.long("color")
.value_parser(value_parser!(Color))
)
.get_matches_from(["myapp", "--color", "always"]);
let color: &Color = matches.get_one::<Color>("color").unwrap();
match color {
Color::Always => println!("Always color"),
Color::Auto => println!("Auto color"),
Color::Never => println!("Never color"),
}
}Custom types implementing FromStr can be used with value_parser!.
ValueParserFactory Trait
use clap::{Arg, Command, builder::{ValueParser, ValueParserFactory}};
use std::str::FromStr;
#[derive(Debug, Clone)]
struct Port(u16);
impl FromStr for Port {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let port: u16 = s.parse().map_err(|_| "invalid port number")?;
if port >= 1024 {
Ok(Port(port))
} else {
Err("port must be >= 1024 (non-privileged)".to_string())
}
}
}
impl ValueParserFactory for Port {
type Parser = ValueParser;
fn value_parser() -> Self::Parser {
ValueParser::new(|s: &str| -> Result<Port, String> {
s.parse()
})
}
}
fn custom_parser_factory() -> Command {
Command::new("myapp")
.arg(
Arg::new("port")
.long("port")
.value_parser(value_parser!(Port))
)
}Implement ValueParserFactory to enable value_parser!(CustomType).
Custom Value Parser Functions
use clap::{Arg, Command, builder::ValueParser};
fn custom_parser() -> Command {
Command::new("myapp")
.arg(
Arg::new("port")
.long("port")
.value_parser(ValueParser::new(|s: &str| -> Result<u16, String> {
let port: u16 = s.parse().map_err(|_| "not a valid port number".to_string())?;
if port < 1024 {
Err("port must be >= 1024 (non-privileged)".to_string())
} else {
Ok(port)
}
}))
)
}
fn use_custom_parser() {
let result = Command::new("myapp")
.arg(
Arg::new("port")
.long("port")
.value_parser(ValueParser::new(|s: &str| -> Result<u16, String> {
let port: u16 = s.parse().map_err(|_| "invalid".to_string())?;
if port < 1024 { Err("privileged port".to_string()) } else { Ok(port) }
}))
)
.try_get_matches_from(["myapp", "--port", "80"]);
match result {
Err(e) => println!("Error: {}", e), // "port must be >= 1024"
Ok(_) => unreachable!(),
}
}ValueParser::new accepts a closure that validates and transforms input.
Range Validation
use clap::{Arg, Command, builder::RangedI64ValueParser};
fn range_validation() -> Command {
Command::new("myapp")
.arg(
Arg::new("level")
.long("level")
.value_parser(RangedI64ValueParser::<i32>::new()
.range(1..=10))
.help("Level between 1 and 10")
)
.arg(
Arg::new("count")
.long("count")
.value_parser(clap::value_parser!(u32).range(1..))
.help("Positive count")
)
}
fn range_errors() {
let result = Command::new("myapp")
.arg(
Arg::new("level")
.long("level")
.value_parser(RangedI64ValueParser::<i32>::new().range(1..=10))
)
.try_get_matches_from(["myapp", "--level", "15"]);
match result {
Err(e) => {
// Error: 15 is not in range 1..=10
println!("{}", e);
}
Ok(_) => unreachable!(),
}
}RangedI64ValueParser validates numeric input against a range.
Multiple Values
use clap::{Arg, Command, value_parser};
fn multiple_values() -> Command {
Command::new("myapp")
.arg(
Arg::new("numbers")
.long("numbers")
.value_parser(value_parser!(i32))
.action(clap::ArgAction::Append)
.help("Multiple numbers (can be repeated)")
)
}
fn use_multiple() {
let matches = Command::new("myapp")
.arg(
Arg::new("numbers")
.long("numbers")
.value_parser(value_parser!(i32))
.action(clap::ArgAction::Append)
)
.get_matches_from(["myapp", "--numbers", "1", "--numbers", "2", "--numbers", "3"]);
let numbers: Vec<&i32> = matches.get_many::<i32>("numbers")
.unwrap()
.collect();
// numbers = [&1, &2, &3]
}action(Append) combined with value_parser handles multiple values.
Default Values and Parsers
use clap::{Arg, Command, value_parser};
fn default_values() -> Command {
Command::new("myapp")
.arg(
Arg::new("count")
.long("count")
.value_parser(value_parser!(u32))
.default_value("10")
)
}
fn use_defaults() {
let matches = Command::new("myapp")
.arg(
Arg::new("count")
.long("count")
.value_parser(value_parser!(u32))
.default_value("10")
)
.get_matches_from(["myapp"]);
let count: u32 = *matches.get_one::<u32>("count").unwrap();
// count = 10 (default value parsed through value_parser)
}Default values are also parsed through value_parser.
PathBuf Parser
use clap::{Arg, Command, value_parser};
use std::path::PathBuf;
fn path_parser() -> Command {
Command::new("myapp")
.arg(
Arg::new("input")
.long("input")
.value_parser(value_parser!(PathBuf))
.help("Input file path")
)
}
fn use_path_parser() {
let matches = Command::new("myapp")
.arg(
Arg::new("input")
.long("input")
.value_parser(value_parser!(PathBuf))
)
.get_matches_from(["myapp", "--input", "/path/to/file.txt"]);
let path: &PathBuf = matches.get_one::<PathBuf>("input").unwrap();
println!("Input: {:?}", path);
}PathBuf parser handles file paths, including OS-specific validation.
Bool Parser
use clap::{Arg, Command, value_parser};
fn bool_parser() -> Command {
Command::new("myapp")
.arg(
Arg::new("verbose")
.long("verbose")
.value_parser(value_parser!(bool))
.action(clap::ArgAction::SetTrue)
)
.arg(
Arg::new("enabled")
.long("enabled")
.value_parser(value_parser!(bool))
.action(clap::ArgAction::Set)
)
}
fn use_bool_parser() {
// --verbose sets to true (no value needed with SetTrue)
let matches = Command::new("myapp")
.arg(
Arg::new("enabled")
.long("enabled")
.value_parser(value_parser!(bool))
.action(clap::ArgAction::Set)
)
.get_matches_from(["myapp", "--enabled", "true"]);
let enabled: bool = *matches.get_one::<bool>("enabled").unwrap();
}bool parser accepts "true", "false", "yes", "no", "1", "0".
Shell Completion Integration
use clap::{Arg, Command, builder::PossibleValuesParser};
fn shell_completion() -> Command {
Command::new("myapp")
.arg(
Arg::new("format")
.long("format")
.value_parser(PossibleValuesParser::new(["json", "yaml", "toml"]))
.help("Output format (shell completion hints these values)")
)
}PossibleValuesParser automatically provides values for shell completion.
Combining with Arg Groups
use clap::{Arg, Command, value_parser, ArgGroup};
fn with_groups() -> Command {
Command::new("myapp")
.arg(
Arg::new("input")
.long("input")
.value_parser(value_parser!(std::path::PathBuf))
)
.arg(
Arg::new("output")
.long("output")
.value_parser(value_parser!(std::path::PathBuf))
)
.group(
ArgGroup::new("io")
.args(["input", "output"])
.required(true)
)
}value_parser works with argument groups and other clap features.
Migration from Validator
use clap::{Arg, Command, builder::ValueParser};
// Old API (deprecated):
fn old_validator() -> Command {
Command::new("myapp")
.arg(
Arg::new("port")
.long("port")
.validator(|s: String| {
let port: u16 = s.parse().map_err(|_| "invalid port")?;
if port >= 1024 { Ok(()) } else { Err("port must be >= 1024".into()) }
})
)
}
// New API (recommended):
fn new_value_parser() -> Command {
Command::new("myapp")
.arg(
Arg::new("port")
.long("port")
.value_parser(ValueParser::new(|s: &str| -> Result<u16, String> {
let port: u16 = s.parse().map_err(|_| "invalid port")?;
if port >= 1024 { Ok(port) } else { Err("port must be >= 1024".into()) }
}))
)
}value_parser replaces validator, validator_os, and possible_values.
Error Handling with Typed Values
use clap::{Arg, Command, value_parser, error::ErrorKind};
fn error_handling() {
let result = Command::new("myapp")
.arg(
Arg::new("count")
.long("count")
.value_parser(value_parser!(u32))
)
.try_get_matches_from(["myapp", "--count", "-10"]);
match result {
Err(e) => {
match e.kind() {
ErrorKind::InvalidValue => {
println!("Invalid value: {}", e);
}
ErrorKind::UnknownArgument => {
println!("Unknown argument: {}", e);
}
_ => {
println!("Other error: {}", e);
}
}
}
Ok(matches) => {
let count: u32 = *matches.get_one::<u32>("count").unwrap();
}
}
}Typed errors provide specific error kinds for different validation failures.
Real-World Example: Network Tool
use clap::{Arg, Command, value_parser, builder::ValueParser};
use std::net::{IpAddr, SocketAddr};
fn network_tool() -> Command {
Command::new("network-tool")
.arg(
Arg::new("address")
.long("address")
.value_parser(value_parser!(IpAddr))
.help("IP address to connect to")
)
.arg(
Arg::new("port")
.long("port")
.value_parser(ValueParser::new(|s: &str| -> Result<u16, String> {
let port: u16 = s.parse().map_err(|_| "invalid port number".to_string())?;
if port >= 1024 || port == 80 || port == 443 {
Ok(port)
} else {
Err("port must be >= 1024 (or 80/443)".to_string())
}
}))
.default_value("80")
)
.arg(
Arg::new("timeout")
.long("timeout")
.value_parser(value_parser!(u64))
.default_value("30")
.help("Timeout in seconds")
)
.arg(
Arg::new("protocol")
.long("protocol")
.value_parser(["tcp", "udp", "http"])
.default_value("tcp")
)
}
fn use_network_tool() {
let matches = network_tool()
.get_matches_from(["network-tool", "--address", "192.168.1.1", "--port", "8080"]);
let address: IpAddr = *matches.get_one::<IpAddr>("address").unwrap();
let port: u16 = *matches.get_one::<u16>("port").unwrap();
let timeout: u64 = *matches.get_one::<u64>("timeout").unwrap();
let protocol: &String = matches.get_one::<String>("protocol").unwrap();
}Complete example with multiple parser types.
Real-World Example: Configuration Override
use clap::{Arg, Command, value_parser, builder::ValueParser};
use std::path::PathBuf;
fn config_tool() -> Command {
Command::new("config-cli")
.arg(
Arg::new("config")
.short('c')
.long("config")
.value_parser(value_parser!(PathBuf))
.help("Configuration file path")
)
.arg(
Arg::new("level")
.short('l')
.long("level")
.value_parser(value_parser!(u8).range(0..=5))
.help("Log level (0-5)")
)
.arg(
Arg::new("mode")
.long("mode")
.value_parser(["debug", "release", "test"])
.default_value("release")
)
.arg(
Arg::new("threads")
.long("threads")
.value_parser(value_parser!(usize))
.default_value("4")
)
}
fn parse_config() {
let matches = config_tool()
.get_matches_from(["config-cli", "-c", "/etc/app.conf", "-l", "3"]);
let config: Option<&PathBuf> = matches.get_one::<PathBuf>("config");
let level: u8 = *matches.get_one::<u8>("level").unwrap_or(&0);
let mode: &String = matches.get_one::<String>("mode").unwrap();
let threads: usize = *matches.get_one::<usize>("threads").unwrap();
}Combined configuration parsing with validation.
Key Points
fn key_points() {
// 1. value_parser transforms string input to typed values
// 2. value_parser!(Type) creates parser for common types
// 3. Built-in parsers: i32, u64, f64, String, PathBuf, bool, etc.
// 4. Custom parsers via closures or ValueParserFactory trait
// 5. PossibleValuesParser for enum-like values
// 6. RangedI64ValueParser for numeric ranges
// 7. Automatic shell completion from possible values
// 8. Replaces validator, validator_os, possible_values APIs
// 9. Values retrieved via get_one::<T>()
// 10. Multiple values via get_many::<T>()
// 11. Default values parsed through value_parser
// 12. Descriptive error messages for validation failures
// 13. Works with ArgGroup, ArgAction, etc.
// 14. PathBuf parser for file paths
// 15. bool parser accepts true/false/yes/no/1/0
}Key insight: value_parser unifies parsing and validation into a single, type-safe API that produces strongly typed values from string command-line input. The parser serves three purposes: converting the raw string to a typed value, validating the value meets constraints, and generating helpful error messages when validation fails. By using value_parser!(T), you get automatic parsing for standard types with built-in error handling. For custom validation, ValueParser::new(closure) lets you implement arbitrary validation logic that returns Result<T, String>. The PossibleValuesParser and range parsers add constraint checking that integrates with shell completion systemsâwhen users tab-complete, the shell suggests valid values. This replaces the older validator and possible_values APIs with a unified approach that's both more ergonomic and more powerful. The typed values retrieved via get_one::<T>() eliminate the need for manual string parsing in application code, pushing validation to the command-line layer where errors are caught early and reported clearly.
