Arguments
Every command-line argument your app accepts starts as a parameter on a Rust function. The type of that parameter tells clish how to parse it. This page covers all the argument types, how to use them, and what happens when things go wrong.
The three kinds of arguments
There are three kinds of arguments in any CLI:
- Positional arguments appear in order, with no flag prefix. Like
productioninmyapp deploy production. - Named options have a
--nameprefix and take a value. Like--env prod. - Flags are boolean toggles. Their presence means true, their absence means false. Like
--force.
clish uses Rust types to distinguish these. Here is the quick reference:
| Type | Kind | Required? | Example |
|---|---|---|---|
Pos<T> |
Positional | Yes | myapp cmd foo |
Pos<Option<T>> |
Positional | No | myapp cmd or myapp cmd foo |
Pos<Vec<T>> |
Positional | No (zero or more) | myapp cmd a b c |
Named<T> |
Named option | Yes | myapp cmd --env prod |
Named<Option<T>> |
Named option | No | |
Named<Vec<T>> |
Named option | No (repeatable) | myapp cmd --tag a --tag b |
bool |
Flag | No | myapp cmd --force |
Let us go through each one.
Positional arguments
A Pos<T> parameter reads values from the command line in order. The first Pos gets the first positional value, the second Pos gets the second, and so on.
#[command]
fn greet(name: Pos<String>, greeting: Pos<String>) {
println!("{greeting}, {name}!");
}
myapp greet Alice "Good morning"
Optional positionals
Wrap the inner type in Option to make a positional argument optional:
#[command]
fn search(query: Pos<String>, limit: Pos<Option<u32>>) {
let max = limit.unwrap_or(10);
println!("Searching for '{query}' (max {max} results)");
}
myapp search "hello" # limit = None, uses default 10
myapp search "hello" 50 # limit = Some(50)
Variadic positionals
Use Pos<Vec<T>> to accept zero or more values. This must be the last positional parameter in your function.
#[command]
fn process(files: Pos<Vec<String>>) {
for file in &files {
println!("Processing {file}");
}
}
myapp process a.txt b.txt c.txt
Named options
A Named<T> parameter reads values from --name value pairs. The name comes from the Rust parameter name by default.
#[command]
fn deploy(env: Named<String>, port: Named<u16>) {
println!("Deploying to {env} on port {port}");
}
myapp deploy --env production --port 8080
Optional named options
#[command]
fn serve(port: Named<Option<u16>>) {
let p = port.unwrap_or(3000);
println!("Serving on port {p}");
}
Repeatable named options
Use Named<Vec<T>> to collect multiple values from the same flag:
#[command]
fn tag(name: Pos<String>, labels: Named<Vec<String>>) {
println!("Tagging {name} with: {labels:?}");
}
myapp tag myapp --labels prod --labels stable --labels v2
Flags
A bool parameter is a flag. If the user passes --flagname, it is true. Otherwise it is false.
#[command]
fn deploy(target: Pos<String>, force: bool, dry_run: bool) {
if dry_run {
println!("Would deploy {target}");
} else if force {
println!("Force deploying {target}");
} else {
println!("Deploying {target}");
}
}
myapp deploy production --force
myapp deploy production --dry-run
Input forms
Named options accept several input forms:
| Form | Example |
|---|---|
--name value |
--env production |
--name=value |
--env=production |
-n value (with short alias) |
-e production |
-nvalue (with short alias) |
-eproduction |
Flags accept these forms:
| Form | Example |
|---|---|
--flag |
--force |
-f (with short alias) |
-f |
| Bundled short flags | -vf (equivalent to -v -f) |
Typed arguments
Any type that implements FromStr works as a type parameter. This means you can use String, i64, u32, f64, bool, PathBuf, or any custom type you define.
#[command]
fn serve(port: Named<u16>, host: Named<String>) {
println!("Serving on {host}:{port}");
}
If the user passes a value that cannot be parsed, clish prints a styled error:
error: invalid value 'abc': expected u16
|
1 | myapp serve --port abc
| ^^^
|
= hint: run 'myapp serve --help' for more information
Compile-time restrictions
Some type combinations do not make sense and are rejected at compile time:
Option<Vec<T>>:Vec<T>already accepts zero or more values, so the outerOptionis redundant.Option<bool>: Flags are already optional by their presence or absence. Just usebool.- Multiple
Pos<Vec<T>>: Only one variadic positional is allowed per command. - Misplaced variadic:
Pos<Vec<T>>must be the last positional parameter.
Short flags and option aliases
You can give named options and flags single-character aliases using param(). This is covered in the Commands page, but here is a quick preview:
#[command(
param(port, short = 'p'),
param(verbose, short = 'v'),
)]
fn serve(port: Named<u16>, verbose: bool) {
println!("Serving on port {port}");
}
Now both of these work:
myapp serve --port 8080 --verbose
myapp serve -p 8080 -v
You can also bundle short flags together:
myapp serve -pv # equivalent to -p 8080 -v (if -p takes a value, it consumes the next arg)
When bundling, only flags can be combined. If a short option appears in a bundle, the parser stops and reports that the option is missing its value.
The -- separator
If you need to pass values that look like flags, use -- to tell clish that everything after it is a positional argument:
myapp process -- -file.txt --not-a-flag
Everything after -- goes into positional parameters, even if it starts with -.
Next step
Now that you know how arguments work, let us look at Commands and how to add metadata like aliases, descriptions, and deprecation notices.