Skip to content

Value Resolution

Every parameter in clish follows a consistent resolution pipeline. This page explains how values flow from the command line through environment variables and defaults to your function.

The resolution pipeline

For any parameter that has env and/or default configured, values are resolved in this order:

CLI argument --> Environment variable --> Default value --> Error

Each step is checked in sequence. The first source that provides a value wins.

Step 1: Command-line argument

If the user passes the argument on the command line, that value is used immediately. No other steps are checked.

myapp deploy --env production    # "production" is used, env var and default ignored

Step 2: Environment variable

If the CLI argument is not provided and the parameter has an env key, clish checks the environment variable:

#[command(param(env, env = "DEPLOY_ENV"))]
fn deploy(env: Named<String>) { ... }
export DEPLOY_ENV=staging
myapp deploy                     # "staging" is used from $DEPLOY_ENV

If the environment variable is not set, the pipeline continues to step 3.

Step 3: Default value

If neither the CLI argument nor the environment variable provided a value and the parameter has a default key, the default is used:

#[command(param(port, default = "8080"))]
fn serve(port: Named<u16>) { ... }
myapp serve                      # port = 8080 (default)
myapp serve --port 3000          # port = 3000 (CLI wins)

Step 4: Error

If the parameter is required (not wrapped in Option) and no value was found from any source, clish prints a missing value error:

error: missing value for option --env
  |
1 | myapp deploy
  |              ^^^^
  |
  = note: --env expects a value

Optional parameters (Named<Option<T>>) skip the error step and resolve to None.

Combining env and default

You can use both env and default on the same parameter:

#[command(
    param(host, env = "DEPLOY_HOST", default = "localhost"),
)]
fn deploy(host: Pos<String>) { ... }

The resolution order is still CLI > env > default > error:

myapp deploy                     # host = "localhost" (default)
export DEPLOY_HOST=prod.example.com
myapp deploy                     # host = "prod.example.com" (env wins over default)
myapp deploy myserver            # host = "myserver" (CLI wins over everything)

Resolution for different parameter types

Required named options (Named<T>)

Source available Result
CLI argument Uses CLI value
No CLI, env set Uses env value
No CLI, no env, default set Uses default
No CLI, no env, no default Error: missing value

Optional named options (Named<Option<T>>)

Source available Result
CLI argument Some(CLI value)
No CLI, env set Some(env value)
No CLI, no env, default set Some(default)
No CLI, no env, no default None

Repeatable named options (Named<Vec<T>>)

Source available Result
CLI argument(s) Vec of CLI values
No CLI, env set Vec with env value
No CLI, no env, default set Vec with default
No CLI, no env, no default Empty Vec

Required positionals (Pos<T>)

Positional parameters do not support env or default in the same way as named options. They are always required and must be provided on the command line. If missing, clish prints a missing argument error.

Optional positionals (Pos<Option<T>>)

If the positional is not provided, it resolves to None. Environment variables and defaults are not typically used with optional positionals.

Variadic positionals (Pos<Vec<T>>)

Collects zero or more values from the command line. Always resolves to a Vec (possibly empty).

Environment variable parsing

When an environment variable is used as a fallback, its string value is parsed the same way as a CLI argument. This means type parsing applies:

#[command(param(port, env = "SERVER_PORT"))]
fn serve(port: Named<u16>) { ... }
export SERVER_PORT=abc
myapp serve
# error: invalid value 'abc': expected u16

Practical patterns

Development defaults with production overrides

#[command(
    param(host, env = "APP_HOST", default = "localhost"),
    param(port, env = "APP_PORT", default = "3000"),
    param(debug, env = "APP_DEBUG"),
)]
fn serve(host: Named<String>, port: Named<u16>, debug: bool) { ... }

In development, defaults apply. In production, set environment variables.

Required with env fallback

#[command(
    param(api_key, env = "API_KEY"),
)]
fn fetch(api_key: Named<String>) { ... }

The user can pass --api-key directly, or set $API_KEY. If neither is provided, an error is shown.

Optional with sensible default

#[command(
    param(format, default = "json", choices = ["json", "yaml", "toml"]),
)]
fn export(format: Named<String>) { ... }

Defaults to JSON, but the user can override with --format yaml.

Next step

Now that you understand how values are resolved, learn about Validation to see how clish enforces choices, conflicts, and prerequisites.