声明一个链

使用 chain 宏声明链,并承接 Entry 输入

上一节我们声明了 `dispatcher!("greet", CMDGreet => EntryGreet)` 现在用户输入 `greet` 时会被匹配并包装成 `EntryGreet`。 但拿到 Entry 之后呢? 我们需要一个 Chain 来处理它。 ## `#[chain]` 宏 `#[chain]` 用来标记一个处理函数,格式非常直接: ```rust @@@dispatcher!("greet", CMDGreet => EntryGreet); pack!(ResultName = String); #[chain] fn handle_greet(args: EntryGreet) -> Next { // args 就是用户输入经过匹配后剩下的参数 let name = args.inner.first().cloned().unwrap_or_else(|| "World".to_string()); // 把结果包装成 Next,告诉调度器下一步去哪 ResultName::new(name) } ``` 注意到了吗? Chain 函数签名里写着它需要什么——`args: EntryGreet` 然后用 `ResultName::new(name)` 返回一个新类型。 这个返回的 `Next` 会展开成 `impl Into>`。 > [!TIP] > 想知道 `Into>` 是怎么工作的? > > 可以去 [任意输出机制](pages/concepts/3-any-output) 章节了解 `ChainProcess`。 ## `pack!` 宏 你大概猜到了,`pack!(ResultName = String)` 定义了一个管线中传递的类型: ```rust // pack!(ResultName = String) 大概生成了这样的代码 #[derive(Groupped)] pub struct ResultName { pub inner: String, } ``` 你可以把它理解为一个 打了标签的 `String`。 调度器通过这个标签来精确路由,确保数据不会混淆 —— 比如发给 `RenderGreet` 的数据不会被误传给 `RenderError`。 > [!NOTE] > 与简单的类型别名 (`type`) 不同,`pack!` 会生成一个全新的类型,拥有独立的 `TypeId`。 命名上推荐这样的习惯: | 角色 | 命名模式 | 示例 | | -------- | ---------------- | -------------------- | | 入口 | `Entry` + 命令名 | `EntryGreet` | | 中间状态 | `State` + 描述 | `StateParsedArgs` | | 最终结果 | `Result` + 描述 | `ResultGreetSomeone` | | 错误 | `Error` + 描述 | `ErrorUserNotFound` | 详见 [命名规范](pages/other/naming_rule),不过现在你只需要记住:**用 `pack!` 给你的数据取一个有意义的名字**。 ## 从 Entry 中提取参数 `EntryGreet` 的 `inner` 是一个 `Vec`,你可以在 Chain 里自由地处理它: ```rust @@@dispatcher!("greet", CMDGreet => EntryGreet); @@@pack!(ResultName = String); #[chain] fn handle_greet(args: EntryGreet) -> Next { // 取第一个参数,没有就用默认值 let name = args .inner .first() .cloned() .unwrap_or_else(|| "World".to_string()); ResultName::new(name) } ``` 如果你启用了 `parser` 特性,还可以用 `Picker` 做更灵活的参数提取,不过那是后话了。 ## 组合起来 现在把 Dispatcher 和 Chain 连在一起: ```rust // 1. 声明命令 dispatcher!("greet", CMDGreet => EntryGreet); // 2. 声明管线中的数据类型 pack!(ResultName = String); // 3. 处理逻辑 #[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!(); ``` 不过这段代码还没写完 —— 我们只有 Dispatcher 和 Chain,还差最后一步:**把结果渲染出来**。这就是下一篇要讲的 Renderer。

Written by @Weicao-CatilGrass