Advanced Overview: How clish Works
Welcome to the advanced guide! If you want to understand how clish operates under the hood, debug complex scenarios, write custom integrations, or contribute to the framework itself, you are in the right place.
This tutorial assumes you know basic programming concepts (variables, functions, types), but you don't need to be a Rust wizard or a systems programming expert. We will explain all advanced terms (like ASTs, linker-based registries, and procedural macros) as we go.
The Big Picture: Compile-Time vs. Run-Time
Many command-line library frameworks (like clap) require you to manually build a command tree at runtime, or use struct tags that parse commands dynamically.
clish takes a different approach: Compile-Time Generation with Zero-Overhead Static Registration.
There are two major phases in a clish application's life:
graph TD
subgraph compile_time ["Compile Time"]
Code[Developer Code] -->|"Annotated with #91;command#93;"| Macro[clish-macros]
Macro -->|Parses function AST| GenCode[Generated Rust Code]
GenCode -->|Declares static metadata| Registry[Linker Section Registry]
end
subgraph run_time ["Run Time"]
Main[fn main] -->|app!| AppRun[App::run]
Registry -->|inventory::iter| Collect[Collect Command Registry]
AppRun -->|Loads| Collect
Argv[std::env::args] -->|Input tokens| Parsing[Parsing Engine]
AppRun --> Parsing
Parsing -->|"Match and Validate"| Dispatch[Dispatch to target closure]
Dispatch -->|Execute| UserFn[Your original function]
end
- Compile Time: When you write
#[command]on a function,clish-macrosruns. It reads your function signature, generates all parsing logic for its parameters, and registers the command statically in the binary. - Run Time: When your compiled program starts up,
clish-coredynamically gathers all registered commands, matches the user's input, parses the tokens, validates choices, and executes the target function.
Crate Layout: Why Three Crates?
If you look at the clish workspace, you will find it is split into three crates:
clish-macros: The procedural macro crate. Procedural macros in Rust are compiler plugins. Rust forces them to be compiled as a separate, distinct crate of typeproc-macro. They cannot run code on your system at runtime; their only job is to transform Rust code tokens into other Rust code tokens during compilation.clish-core: The runtime library. This contains the parser engine, style configurations, validation checks, and error rendering routines. It has no macros, only standard Rust code.clish: The public-facing entry point. It depends on bothclish-coreandclish-macros. It re-exports all their public types (likePos,Named,App, and the#[command]macro) under a single namespace. This is the only crate you add to yourCargo.toml.
What We Will Cover
To help you fully understand the mechanics of clish, we have split the internals guide into five specialized chapters:
- Macro Internals: How
clish-macrosinspects your code tokens and usessynandquoteto generate code, plus how it registers commands using theinventorycrate. - Parsing Engine: Inside the tokenization loop, flag bundling, option resolution, and type conversions.
- Error Pipeline: How errors flow across the boundary between the generated parser and the runtime print layout, and how they point directly to CLI syntax mistakes.
- Help Rendering: The mechanics of measuring, styling, and formatting the responsive command help screens.
- App Dispatch: How the top-level execution loop gathers commands, checks version flags, and invokes subcommands.
Let's begin by looking at Macro Internals to see how Rust code becomes a CLI layout.