DEEP 08 · 第一辑 · 运动控制核心 · LEARN NOTES

收进学习手册 · 2026-09-11 · duck.whatled.com

解读 08 · lib.rs 与 crate 组织:控制库的分层

解读对象:duck-control/src/lib.rs(25 行)及两个 Cargo.toml
需要的前置:Rust 的 crate(编译单元)与模块概念;各模块细节见解读 01–解读 07

1. 25 行的 lib.rs,主要内容是一句"不"

整个文件只有模块声明和再导出,开头的模块注释是这个库的宪法(lib.rs:1-5):"Deliberately not a daemon. There is no tokio here, no socket, no systemd — robotd owns all of that. The boundary is enforced by the compiler rather than by discipline."——刻意不做守护进程:没有 tokio(异步运行时)、没有 socket、没有 systemd,进程层的事全归 robotd。关键在最后半句:边界靠编译器而不是靠纪律——crate 里根本没有那些依赖,想在这层开 socket 都写不出来。

接着列出控制路径:model、bus、io::RobotIo、observations、policy、safety(lib.rs:7-8),注明设计出处是 docs/design/robotd-design.md §2。其余是 pub mod 声明(bus、fall、imu、io、model、obs、policy、safety)和 pub use 再导出(lib.rs:19-25)——把 battery_percent、Sensors 等常用类型顶到 crate 根,下游写 duck_control::Sensors 而不必背模块路径。

2. 一条不寻常的依赖边

duck-control 依赖 duck-ipc-proto——一个 IPC 协议 crate,控制库为什么依赖通信协议?Cargo.toml 注释值得当范文读:为了 JOINT_NAMES 这一个常量。关节顺序就是协议——状态流里的 joints、targets 是裸数组,按下标索引,所以名字表应住在每个客户端都已链接的 crate 里,由本 crate 再导出,"而不是留两份副本手工同步"。注释同时堵住误读:"Not a door onto IPC: nothing here speaks to a socket"——依赖是单常量的,没有打开任何通信能力;robotd 本就同时链接两个 crate,没有二进制因此多出一条依赖边。

这是"一个常量的真相来源放哪"的答案:放在所有消费者共有的最低层。

3. Cargo.toml 里的三个工程决定

依赖列表本身也是文档(duck-control/Cargo.toml):

4. 守护进程那边的镜像

robotd/Cargo.toml 是同一枚硬币的反面:依赖 duck-control、robotd-params、duck-ipc-proto、kinematics、sounds 等;arc-swap 的注释一句——"无锁单值槽,控制循环每拍一次原子 load,永远不会被 IPC 任务卡住"。更讲究的是 dev-dependencies(仅测试链接):updater 出现在这里,因为有测试要驱动真实更新引擎对着真 robotd 二进制验证契约;注释解释了测试为何住在 robotd 包里——只有这里 cargo 定义 CARGO_BIN_EXE_robotd 并保证二进制是新的,放在 updater 那边就得猜路径。

你带走的收获

延伸

什么鸭 WHATDUCK · 复刻学习笔记 ← 全部手册 · 首页