core:args
Command-line argument parsing (WEP wep-2026-06-22-core-args.md).
core:args is a non-self-describing, parse-only Deserializer over argv,
peer to core:json_nsd. Argument types are ordinary structs with
impl Deserialize for T; — no bespoke derive. Struct fields become --long
options; fields marked #[serde(positional)] are filled from bare tokens in
declaration order. Scalar tokens are converted with LenientFromStr.
Supported: --name value, --name=value, bool flags (--name),
Option<T> = null, required/optional/variadic positionals, repeatable
List<T> options (--inc a --inc b, interspersing allowed), and the --
end-of-options marker. -/_ fold, so --dry-run binds dry_run.
Long options only: an option value never starts with --, so a missing
value (--name --next) is reported as MissingValue rather than swallowing
the next flag; a single - is a value, so negatives like -5 work. A
repeatable option always takes a value (there is no repeatable flag);
--flag with no value is MissingValue.
Subcommands are a #[serde(positional)] field whose type is a variant:
the leading bare token selects the case (externally tagged) and the rest is
the payload, so command [global-opts] subcommand [sub-opts] works and
nesting is free. The tag matches the case's wire name; keep the cases
idiomatic PascalCase and apply #[serde(rename_all = "kebab-case")] (or
rename per case) for lowercase/kebab subcommands like add-remote.
Positional declaration order is not validated
Positional fields are bound left-to-right by declaration order (rank), and
that binding is greedy. core:args does not check the schema for
well-formedness, so a few declarations parse in surprising ways — keep these
invariants yourself (the compiler will not flag a violation; you get a
confusing MissingArgument, not a crash):
- Declare required positionals before optional (defaulted) ones. An optional
positional placed first greedily takes the token, starving a later required
one. (This footgun is shared by any ordinal format, e.g.
core:json_nsd.) - At most one variadic (
List<T>) positional, and it must be last. The first variadic consumes every remaining bare token, leaving later positionals empty or unfilled. - Do not combine a variadic positional with a subcommand in one struct: the variadic would swallow the subcommand tag token.
Validation is intentionally omitted: it is a CLI-specific well-formedness rule, not a data-model property, so it stays out of the format-agnostic compiler layer; and at parse time the deserializer sees only field indices, not their types or arity, so it cannot self-check (only over-supply — "too many positionals" — is caught). See the WEP for the rationale.
Synopsis
struct Options {
#[serde(positional)]
input: String,
verbose: bool = false,
jobs: i32 = 1,
}
impl Deserialize for Options;
assert parse::<Options>(["in.txt", "--verbose", "--jobs", "4"]) matches { Ok(opts) && opts.input == "in.txt"
&& opts.verbose
&& opts.jobs == 4 };
Functions
pub fn parse<T: Deserialize>(argv: List<String>) -> Result<T, ArgsError>
Parse argv (excluding the program name) into T. Effect-free and testable;
subcommand dispatch and tests inject a List<String> directly.
pub fn from_env<T: Deserialize>() -> Result<T, ArgsError> with Environment
Parse the process arguments (from core:cli::args(), with the program name
dropped) into T.
Structs
pub struct ArgsError
A command-line parse failure.
kind: ArgsErrorKind
message: String
pub struct ArgvDeserializer
The argv Deserializer. Holds the token stream and the plumbing that serves
the current field's value to the deserialize_* methods.
Fields are private.
impl Deserializer for ArgvDeserializer
fn deserialize_i32(&mut self) -> Result<i32, DeserializeError>
fn deserialize_i64(&mut self) -> Result<i64, DeserializeError>
fn deserialize_u32(&mut self) -> Result<u32, DeserializeError>
fn deserialize_u64(&mut self) -> Result<u64, DeserializeError>
fn deserialize_i128(&mut self) -> Result<i128, DeserializeError>
fn deserialize_u128(&mut self) -> Result<u128, DeserializeError>
fn deserialize_f32(&mut self) -> Result<f32, DeserializeError>
fn deserialize_f64(&mut self) -> Result<f64, DeserializeError>
fn deserialize_bool(&mut self) -> Result<bool, DeserializeError>
fn deserialize_char(&mut self) -> Result<char, DeserializeError>
fn deserialize_string(&mut self) -> Result<String, DeserializeError>
fn is_null(&mut self) -> Result<bool, DeserializeError>
fn begin_seq(&mut self) -> Result<ArgvSeqAccess, DeserializeError> with stores[self]
fn begin_map(&mut self) -> Result<ArgvMapAccess, DeserializeError> with stores[self]
fn begin_struct(&mut self, name: &String, num_fields: i32) -> Result<ArgvStructAccess, DeserializeError> with stores[self]
fn begin_variant(&mut self, type_name: &String, num_cases: i32) -> Result<ArgvVariantAccess, DeserializeError> with stores[self]
fn deserialize_any<V: Visitor>(&mut self, visitor: &mut V) -> Result<V::Value, DeserializeError>
Enums
pub enum ArgsErrorKind
Error categories surfaced by core:args.
