1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
|
<h1 align="center">Declare a Chain</h1>
<p align="center">
Use the <code>chain</code> macro to declare a chain and handle Entry input
</p>
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<ChainProcess<ThisProgram>>`.
> [!TIP]
> Wondering how `Into<ChainProcess<G>>` 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<String>`, 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.
<p align="center" style="font-size: 0.85em; color: gray;">
Written by @Weicao-CatilGrass
</p>
|