Declare a Chain

Use the chain macro to declare a chain and handle Entry input

In the previous section, we declared `dispatcher!("greet", CMDGreet => EntryGreet)`. Now when a user types `greet`, it gets matched and wrapped into `EntryGreet`. But what happens after we get the Entry? We need a Chain to process it. ## The `#[chain]` Macro `#[chain]` marks a handler function. The format is straightforward: ```rust @@@dispatcher!("greet", CMDGreet => EntryGreet); pack!(ResultName = String); #[chain] fn handle_greet(args: EntryGreet) -> Next { // args contains the remaining params after matching user input let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string()); // Wrap the result into Next, telling the dispatcher where to go next ResultName::new(name) } ``` Notice anything? The Chain function signature declares what it needs — `args: EntryGreet`. Then it returns a newtype via `ResultName::new(name)`. This returned `Next` expands into `impl Into>`. > [!TIP] > Wondering how `Into>` works? > > Check out the [Any Output Mechanism](pages/concepts/3-any-output) chapter to learn about `ChainProcess`. ## The `pack!` Macro You've probably guessed it — `pack!(ResultName = String)` defines a type that flows through the pipeline: ```rust // pack!(ResultName = String) generates code roughly like this #[derive(Groupped)] pub struct ResultName { pub inner: String, } ``` Think of it as a **tagged** `String`. The dispatcher uses this tag for precise routing, ensuring data doesn't get mixed up — e.g., data sent to `RenderGreet` won't be misdelivered to `RenderError`. > [!NOTE] > Unlike a simple type alias (`type`), `pack!` generates a completely new type with its own `TypeId`. Here's a recommended naming convention: | Role | Naming Pattern | Example | | ------------ | ---------------------- | -------------------- | | Entry | `Entry` + command | `EntryGreet` | | Intermediate | `State` + description | `StateParsedArgs` | | Result | `Result` + description | `ResultGreetSomeone` | | Error | `Error` + description | `ErrorUserNotFound` | See [Naming Convention](pages/other/naming_rule) for details, but for now just remember: **use `pack!` to give your data a meaningful name**. ## Extracting Params from Entry `EntryGreet`'s `inner` is a `Vec`, which you can freely process inside a Chain: ```rust @@@dispatcher!("greet", CMDGreet => EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet(args: EntryGreet) -> Next { // Take the first param, or use a default let name = args .inner .first() .cloned() .unwrap_or_else(|| "World".to_string()); ResultName::new(name) } ``` If you enable the `parser` feature, you can also use `Picker` for more flexible param extraction — but that's a topic for later. ## Putting It Together Now let's connect the Dispatcher and Chain: ```rust // 1. Declare the command dispatcher!("greet", CMDGreet => EntryGreet); // 2. Declare the pipeline data type pack!(ResultName = String); // 3. Processing logic #[chain] fn handle_greet(args: EntryGreet) -> Next { let name = args.inner .first() .cloned() .unwrap_or_else(|| "World".to_string()); ResultName::new(name) } fn main() { let mut program = ThisProgram::new(); program.with_dispatcher(CMDGreet); program.exec_and_exit(); } gen_program!(); ``` But this code isn't complete yet — we only have the Dispatcher and Chain. One last step remains: **rendering the result**. That's what the next chapter, Renderer, covers.

Written by @Weicao-CatilGrass