How does clap::ArgMatches::get_raw differ from get_one for accessing argument values without parsing?
get_raw returns the raw string values from the command line without any parsing or validation, while get_one<T> parses the argument value as type T and returns a typed reference. Use get_raw when you need the original string representationâperhaps to implement custom parsing logic, handle edge cases that clap's built-in parsers don't support, or preserve the exact input for logging or debugging. Use get_one<T> for the common case where you want clap to parse values into types like i32, PathBuf, or custom types via ValueParser. The key difference is that get_raw bypasses all of clap's type parsing machinery and gives you direct access to what the user typed.
Basic Value Access
use clap::{Arg, Command};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("number")
.long("number")
.value_parser(clap::value_parser!(i32)))
.arg(Arg::new("name")
.long("name"))
.get_matches_from(&["example", "--number", "42", "--name", "Alice"]);
// get_one<T>: parsed value with type safety
let number: i32 = *matches.get_one::<i32>("number").unwrap();
println!("Number (parsed): {}", number);
// get_raw: raw string values
let raw_number = matches.get_raw("number").unwrap().next().unwrap();
println!("Number (raw): {}", raw_number.to_string_lossy());
// For strings, get_one returns &String
let name: &String = matches.get_one::<String>("name").unwrap();
println!("Name (parsed): {}", name);
// get_raw returns the OsStr directly
let raw_name = matches.get_raw("name").unwrap().next().unwrap();
println!("Name (raw): {}", raw_name.to_string_lossy());
}get_one provides parsed values; get_raw provides raw OsString values.
The Type Parameter
use clap::{Arg, Command};
use std::path::PathBuf;
fn main() {
let matches = Command::new("example")
.arg(Arg::new("path")
.long("path")
.value_parser(clap::value_parser!(PathBuf)))
.arg(Arg::new("count")
.long("count")
.value_parser(clap::value_parser!(u32)))
.get_matches_from(&["example", "--path", "/tmp/test", "--count", "100"]);
// get_one uses type parameter to determine parsing
let path: &PathBuf = matches.get_one::<PathBuf>("path").unwrap();
println!("Path: {:?}", path);
let count: u32 = *matches.get_one::<u32>("count").unwrap();
println!("Count: {}", count);
// If you request wrong type, you'll get None (runtime)
// This would panic - type doesn't match value_parser
// let wrong: &i32 = matches.get_one::<i32>("path").unwrap();
// get_raw doesn't care about types
let raw_path = matches.get_raw("path").unwrap().next().unwrap();
println!("Raw path: {:?}", raw_path);
}get_one<T> relies on the value_parser configuration; get_raw ignores it.
Custom Parsing with get_raw
use clap::{Arg, Command};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("range")
.long("range"))
.get_matches_from(&["example", "--range", "10..20"]);
// get_raw lets you implement custom parsing
let raw = matches.get_raw("range").unwrap().next().unwrap();
let raw_str = raw.to_string_lossy();
// Parse a range notation that clap doesn't natively support
let parts: Vec<&str> = raw_str.split("..").collect();
let start: i32 = parts[0].parse().unwrap();
let end: i32 = parts[1].parse().unwrap();
println!("Range: {} to {}", start, end);
// Alternative: use clap's value_parser with custom type
// But get_raw is simpler for one-off custom parsing
}get_raw enables custom parsing logic beyond clap's built-in parsers.
OsStr vs String Handling
use clap::{Arg, Command};
use std::ffi::OsStr;
fn main() {
let matches = Command::new("example")
.arg(Arg::new("file")
.long("file"))
.get_matches_from(&["example", "--file", "test.txt"]);
// get_raw returns OsStr (preserves OS encoding)
let raw_values = matches.get_raw("file").unwrap();
for value in raw_values {
// OsStr may contain non-UTF8 data
match value.to_str() {
Some(s) => println!("UTF-8: {}", s),
None => println!("Non-UTF-8: {:?}", value),
}
}
// get_one::<String> requires valid UTF-8
// Returns None if OsStr isn't valid UTF-8
if let Some(s) = matches.get_one::<String>("file") {
println!("Parsed as String: {}", s);
}
}get_raw preserves OsStr encoding; get_one::<String> requires UTF-8.
Multiple Values
use clap::{Arg, Command, ArgAction};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("values")
.long("value")
.action(ArgAction::Append))
.get_matches_from(&["example", "--value", "a", "--value", "b", "--value", "c"]);
// get_one returns first value only
let first: &String = matches.get_one::<String>("values").unwrap();
println!("First value: {}", first);
// For multiple values with get_one, use get_many
let all_values: Vec<&String> = matches.get_many::<String>("values")
.unwrap()
.collect();
println!("All values (parsed): {:?}", all_values);
// get_raw returns all values as OsStr
let raw_values: Vec<&std::ffi::OsStr> = matches.get_raw("values")
.unwrap()
.collect();
println!("All values (raw): {:?}", raw_values);
// Count of values
let count = matches.get_raw("values").unwrap().count();
println!("Count: {}", count);
}get_raw returns an iterator over all values; get_one returns a single value.
Working with Flags
use clap::{Arg, Command, ArgAction};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("verbose")
.short('v')
.action(ArgAction::Count))
.get_matches_from(&["example", "-v", "-v", "-v"]);
// Flags with action Count return the count
let count: u8 = *matches.get_one::<u8>("verbose").unwrap();
println!("Verbosity count: {}", count);
// get_raw for flags: empty iterator (no values)
// Flags don't have values, just presence
let raw = matches.get_raw("verbose");
match raw {
Some(iter) => println!("Raw values: {:?}", iter.collect::<Vec<_>>()),
None => println!("Flag not present"),
}
// Check flag presence
let present = matches.contains_id("verbose");
println!("Verbose present: {}", present);
}get_raw returns values for arguments that accept values; flags may behave differently.
Error Handling Differences
use clap::{Arg, Command};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("count")
.long("count")
.value_parser(clap::value_parser!(i32)))
.get_matches_from(&["example", "--count", "abc"]);
// Note: this would actually fail during parsing
// clap validates during get_matches_from
// For demonstration, let's see what happens with valid input
let matches = Command::new("example")
.arg(Arg::new("count")
.long("count")
.value_parser(clap::value_parser!(i32)))
.get_matches_from(&["example", "--count", "42"]);
// get_one returns Option<&T>
// None if argument not present or type mismatch
let count: Option<&i32> = matches.get_one("count");
println!("Parsed count: {:?}", count);
// get_raw returns Option<OsStrIter>
// None if argument not present
let raw: Option<_> = matches.get_raw("count");
match raw {
Some(iter) => {
let values: Vec<_> = iter.collect();
println!("Raw values: {:?}", values);
}
None => println!("No values"),
}
// Non-existent argument
let missing: Option<&i32> = matches.get_one("nonexistent");
let raw_missing = matches.get_raw("nonexistent");
println!("Missing parsed: {:?}", missing); // None
println!("Missing raw: {:?}", raw_missing); // None
}Both return Option; get_one for missing or unparsable, get_raw for missing only.
Custom Type Parser
use clap::{Arg, Command, builder::ValueParser};
use std::ffi::OsString;
#[derive(Debug)]
struct Port(u16);
fn parse_port(arg: &OsString) -> Result<Port, String> {
let s = arg.to_string_lossy();
let num: u16 = s.parse().map_err(|_| "invalid number")?;
if num >= 1024 {
Ok(Port(num))
} else {
Err("port must be >= 1024".to_string())
}
}
fn main() {
let matches = Command::new("example")
.arg(Arg::new("port")
.long("port")
.value_parser(ValueParser::new(parse_port)))
.get_matches_from(&["example", "--port", "8080"]);
// get_one returns the parsed custom type
let port: &Port = matches.get_one::<Port>("port").unwrap();
println!("Port: {:?}", port);
// get_raw still returns the raw string
let raw = matches.get_raw("port").unwrap().next().unwrap();
println!("Raw: {:?}", raw);
// Invalid port would fail during parsing
// let invalid = Command::new("example")
// .arg(Arg::new("port")
// .long("port")
// .value_parser(ValueParser::new(parse_port)))
// .try_get_matches_from(&["example", "--port", "80"]);
// // Returns Err due to validation failure
}get_one returns validated types; get_raw bypasses custom validation.
Practical Use Cases for get_raw
use clap::{Arg, Command};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("config")
.long("config"))
.arg(Arg::new("format")
.long("format")
.value_parser(["json", "yaml", "toml"]))
.get_matches_from(&["example", "--config", "app.json", "--format", "json"]);
// Use case 1: Preserve exact input for logging
if let Some(raw_values) = matches.get_raw("config") {
for value in raw_values {
println!("User provided config path: {:?}", value);
}
}
// Use case 2: Handle paths with special characters
// OsStr handles paths that might not be valid UTF-8
if let Some(raw_path) = matches.get_raw("config").and_then(|mut i| i.next()) {
// Use as OsStr for file operations
println!("Path as OsStr: {:?}", raw_path);
}
// Use case 3: Implement fallback parsing
let format_raw = matches.get_raw("format")
.and_then(|mut i| i.next())
.map(|s| s.to_string_lossy().into_owned());
// Custom validation beyond clap's built-in
if let Some(format) = format_raw {
// Additional processing
let normalized = format.to_lowercase();
println!("Normalized format: {}", normalized);
}
// Use case 4: Debugging CLI parsing
println!("Raw arguments provided:");
for id in matches.ids() {
if let Some(values) = matches.get_raw(&id) {
let values: Vec<_> = values.collect();
println!(" {}: {:?}", id.as_str(), values);
}
}
}get_raw is useful for logging, debugging, and handling edge cases.
Comparison with get_many
use clap::{Arg, Command, ArgAction};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("items")
.long("item")
.action(ArgAction::Append))
.get_matches_from(&["example", "--item", "a", "--item", "b", "--item", "c"]);
// get_one: first value only
let first: &String = matches.get_one::<String>("items").unwrap();
println!("First: {}", first);
// get_many<T>: all values, parsed
let parsed_items: Vec<&String> = matches.get_many::<String>("items")
.unwrap()
.collect();
println!("All parsed: {:?}", parsed_items);
// get_raw: all values, raw OsStr
let raw_items: Vec<&std::ffi::OsStr> = matches.get_raw("items")
.unwrap()
.collect();
println!("All raw: {:?}", raw_items);
// Summary:
// - get_one<T>: first value, parsed as T
// - get_many<T>: all values, parsed as T
// - get_raw: all values, raw OsStr, no parsing
}get_many<T> is the typed multi-value counterpart to get_one<T>.
Complete Example
use clap::{Arg, Command, ArgAction};
fn main() {
let matches = Command::new("tool")
.about("A command-line tool")
.arg(Arg::new("input")
.short('i')
.long("input")
.value_name("FILE")
.required(true))
.arg(Arg::new("count")
.short('c')
.long("count")
.value_parser(clap::value_parser!(u32))
.default_value("1"))
.arg(Arg::new("verbose")
.short('v')
.action(ArgAction::Count))
.arg(Arg::new("values")
.long("value")
.action(ArgAction::Append))
.get_matches_from(&[
"tool",
"--input", "data.txt",
"--count", "10",
"--value", "x",
"--value", "y",
"-vvv"
]);
// Typed access with defaults
let count: u32 = *matches.get_one::<u32>("count").unwrap();
println!("Count: {}", count);
// Required argument
let input: &String = matches.get_one::<String>("input").unwrap();
println!("Input: {}", input);
// Multiple values with type parsing
let values: Vec<&String> = matches.get_many::<String>("values")
.map(|v| v.collect())
.unwrap_or_default();
println!("Values: {:?}", values);
// Flag count
let verbose: u8 = *matches.get_one::<u8>("verbose").unwrap_or(&0);
println!("Verbose: {}", verbose);
// Raw access for all arguments
println!("\nRaw argument values:");
for id in matches.ids() {
println!(" {}:", id.as_str());
if let Some(raw_values) = matches.get_raw(&id) {
for value in raw_values {
println!(" {:?}", value);
}
}
}
}A complete example showing both approaches in a realistic application.
Synthesis
Quick reference:
use clap::{Arg, Command};
fn main() {
let matches = Command::new("example")
.arg(Arg::new("number")
.long("number")
.value_parser(clap::value_parser!(i32)))
.arg(Arg::new("name")
.long("name"))
.get_matches_from(&["example", "--number", "42", "--name", "Alice"]);
// get_one<T>: parsed, typed, first value only
let number: i32 = *matches.get_one::<i32>("number").unwrap();
let name: &String = matches.get_one::<String>("name").unwrap();
// get_raw: raw OsStr, all values, no parsing
let raw_number = matches.get_raw("number")
.and_then(|mut i| i.next())
.map(|s| s.to_string_lossy().into_owned());
let raw_values: Vec<_> = matches.get_raw("name")
.map(|i| i.collect())
.unwrap_or_default();
// Use get_one when:
// - You want type-safe parsed values
// - Using built-in or custom value parsers
// - Single value per argument
// Use get_many<T> when:
// - Multiple values, still want type parsing
// Use get_raw when:
// - You need the original OsStr
// - Implementing custom parsing
// - Handling non-UTF8 input
// - Debugging CLI parsing
// - Preserving exact input for logging
}Key insight: get_one<T> and get_raw represent two different philosophies of argument handling. get_one<T> embraces clap's type systemâyou declare expected types via value_parser, and clap handles parsing, validation, and type conversion. This is the recommended path for most use cases. get_raw opts out of that system entirely, giving you direct access to the OsStr values as they appeared on the command line. This is essential when dealing with paths that might contain non-UTF-8 characters, when you need to implement parsing logic that doesn't fit into clap's ValueParser framework, or when you want to preserve the exact input for logging or debugging. The return types also differ: get_one<T> returns Option<&T> (a single typed value), while get_raw returns Option<OsStrIter> (an iterator over raw values, potentially multiple). For multiple typed values, use get_many<T> which combines the benefits of type parsing with multi-value support.
