Skip to content

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 production in myapp deploy production.
  • Named options have a --name prefix 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 outer Option is redundant.
  • Option<bool>: Flags are already optional by their presence or absence. Just use bool.
  • 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.