生态地图:crates.io 与三种开发范式
上一章你拆了 Cargo 项目的骨架;这一章往外看——[dependencies] 里的名字从哪来、怎么挑,以及"程序想活成什么样"如何决定你拉哪些箱。读完你能回答:
- 给你
jackdaw/Cargo.toml的依赖段,你能对每个箱说出一句"它管什么"; - AI 推荐一个你没听过的箱,你知道去哪三个站点查、看哪三个信号;
- 听到 "no_std""交叉编译",你能说出它属于哪种开发范式、对依赖清单意味着什么。
查箱:crates.io、docs.rs、lib.rs
Rust 的依赖不用手动下载:在 Cargo.toml 里写一行名字和版本要求,Cargo 就去 crates.io 拉。它是官方包仓库,角色相当于 npm 之于 JavaScript。类比有一处关键失配:npm 发布后 72 小时内还能下架版本,crates.io 的版本一经发布永不删除——Cargo.lock 承诺的可复现构建,押的就是这份不变性(第 8 章)。发布单位叫包(package),编译单位叫箱(crate)——第 8 章拆过的 jackdaw,就是一个包里同时装着库箱和二进制箱。
查一个箱,三个入口:crates.io 看版本历史、下载量、仓库链接;docs.rs 看每个发布版本自动生成的 API 文档,这是"教读"课的主战场;lib.rs 是第三方索引站,把散落各处的质量信号聚在一页。判断敢不敢用,看三个信号:维护(最近发布是上周还是三年前、issue 有没有人回)、下载量(总量看规模、近期看活跃)、MSRV(箱要求的最低 Rust 版本,太新的箱可能拒绝你的工具链)。下载量高不等于合适你——先读文档,再看数据。
TIP
读 docs.rs 的顺序:先扫类型与函数签名,再看页首示例;文档里的代码块会随发布自动编译验证,坏例子活不到上线。让 AI 写代码时,贴 docs.rs 链接比贴一篇教程有效。
jackdaw 的十三个箱:一行一个理由
# ...
# Dependencies are kept to the minimum the blueprint lists: nothing here is
# "nice to have" — each crate appears because a spec behavior needs it.
[dependencies]
tokio = { version = "1", features = ["full"] }
axum = "0.8"
reqwest = { version = "0.12", features = ["json", "stream"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
toml = "0.9"
clap = { version = "4", features = ["derive"] }
thiserror = "2"
anyhow = "1"
async-trait = "0.1"
futures-util = "0.3"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }读法:从 tokio 那行看起。每个条目是"名字 = 版本要求,外加可选的 features"。版本要求不是精确值:"1" 是 1.x 都行、"0.8" 是 0.8.x——第 8 章讲过的语义化版本规则。特性(feature)是箱的功能开关:reqwest 只开 json 和 stream,因为 jackdaw 只发 JSON 请求、只收流式响应,没点名的功能根本不参与编译。这份清单第 8 章只逐行读了前九行(# ... 截断处即止),这里补齐后四个、看全貌。紧贴 [dependencies] 上方那两行注释是第 8 章读过的作者自辩:每个箱都对应规格里的一个行为,没有"以后也许有用"。
十三个箱,各一句话:
- tokio — 异步运行时(runtime):驱动所有
.await的调度器(第 7 章); - axum — HTTP 服务框架:网关监听端口、分发请求(第 14 章展开);
- reqwest — HTTP 客户端:替你敲 provider 的门(第 12 章展开);
- serde — 序列化框架:数据结构与文件格式之间的翻译规则(第 6 章初见);
- serde_json — serde 的 JSON 后端:聊天请求与响应的格式;
- toml — serde 的 TOML 后端:读
jackdaw.toml配置(第 11 章展开); - clap — 命令行参数解析:子命令与选项的定义(第 10 章展开);
- thiserror — 给库定义错误类型的宏:一行一个变体,能被
match穷尽; - anyhow — 二进制一侧的错误容器:什么都装得下,让
?一路向上; - async-trait — 让 trait 里能写
async fn(第 7 章讲过为什么需要它); - futures-util — 异步工具箱:主要用它的流适配器逐块读 SSE(第 12 章展开);
- tracing / tracing-subscriber — 结构化日志两件套:前者埋点,后者输出到终端、按环境变量过滤。
三种范式:程序怎么活
拿到任何 Rust 项目,先问一个问题:这个程序怎么活?有三种答案,对应三种开发范式。
后端服务(axum + tokio):长驻进程,启动后不退出,活在请求循环里。它关心的事和写 Node/Go 服务一样:连接池(复用到上游的连接,不必每个请求重新握手)、优雅停机(收到终止信号,先拒新请求、送完手头的再退)。
桌面与 CLI 应用(clap、Tauri):进程短命,跑完就退。它对外的界面是退出码(0 成功、非 0 失败,shell 靠它分支)和输出流;它计较启动速度与安装体积。Tauri 是这条线在桌面端的延伸:用系统 WebView 打包前端,不往安装包里塞浏览器。
系统级库(no_std、FFI、嵌入式):根本没有进程。它被链接进别人的固件或程序,环境可能没有操作系统、没有堆分配器——no_std 就是不带标准库的运行环境,连 String、Vec 都默认不可用;它的出口是 FFI(外部函数接口),供 C 侧调用。
| 后端服务 | 桌面与 CLI | 系统级库 | |
|---|---|---|---|
| 生命周期 | 长驻,活在请求循环 | 一跑即退 | 无进程概念 |
| 对外界面 | HTTP、流式响应 | 退出码、stdout | C ABI |
| 代表依赖 | axum、tokio | clap、Tauri | no_std、无运行时 |
范式不是品味,是硬约束,它反着裁剪依赖清单:no_std 固件不可能依赖 tokio——运行时要线程、网络栈和堆,固件那头可能一样都没有。jackdaw 横跨前两种:命令行入口(clap)是 CLI,serve 起来的网关是长驻服务,所以依赖里 clap 和 axum 同时在场。反过来看依赖,就能猜到项目站在哪条线。
发布:release profile 与交叉编译
第 8 章你见过 target/debug 与 target/release:同一项目、两种姿态——dev 快编译,release 重优化。发布 profile就是这组产物取向的开关。jackdaw 对 release 提了两个要求:
# ...
# Shipping profile: small binaries beat last-percent compile speed for a
# teaching project you build once and copy around.
[profile.release]
lto = true
strip = true读法:这段注释与 [profile.release] 同第 8 章发布配置节是同一份,取舍已经读过:编译一次、拷着到处跑,宁要小二进制,不要快编译——这里只看新细节。lto = true 开链接期优化,允许编译器跨箱内联:编译更慢,产物更小更快;strip = true 剥掉符号表(函数与变量的名字索引):体积再小一截,代价是崩溃堆栈不再可读。以后你读任何项目的 [profile.*] 段,它都在回答"作者要编译器优化什么"。
交叉编译(cross-compilation)是最后一块拼图:在你的 MacBook 上,产出跑在 Linux 服务器上的二进制——rustup target add 加目标平台,cargo build --release --target <triple> 指定目标。这是工具链的内建能力,不是外挂。这里简化了:纯 Rust 的目标一条命令就够,要 C 链接器的目标还差一步(第 15 章展开),本课只需要你知道这个概念。
小结
- 查箱三入口:crates.io(元数据)、docs.rs(API 文档)、lib.rs(聚合);可靠性三信号:维护、下载量、MSRV。
- jackdaw 的十三个箱按职能分组:运行时、HTTP 两端、序列化三件套、命令行、错误两件、异步补件、日志两件。
- 三种范式按"程序怎么活"分:长驻服务、跑完即退的应用、无进程的库;范式决定哪些依赖根本进不来。
- release profile 管产物取舍(lto/strip),交叉编译让一台机器产出另一个平台的二进制。
下一章进入 Part 4:jackdaw 从零长出来,第一刀是那个会说话的 CLI。
自检
迁移:你写过 Python——HTTP 客户端用
requests,命令行用argparse,JSON 用标准库json。到 Rust 各换成什么箱?哪一项的答案和 Python 相比最意外?预测:AI 提议"把优化也带给日常开发",给出这个 diff——接受它之后,你每天
cargo run的体验会变成什么样?diff[profile.release] lto = true strip = true +[profile.dev] +lto = true +strip = true解释:同事想把 jackdaw 直接编进一个 no_std 的传感器固件当内置网关。只看上面的依赖清单,这条路卡在哪、为什么?
参考答案
requests→reqwest;argparse→clap;json→serde+serde_json。最意外的是第三项:Rust 标准库刻意保持小,连 JSON 都要从 crates.io 拉——这是生态的默认分工,不是缺斤短两。- dev profile 的存在理由是快编译、可调试。
lto会拖慢每次cargo build/cargo run(包括增量编译),strip让崩溃堆栈只剩一串地址。你会得到一个又慢又难调的日常工具链——这两个开关属于 release。 - 卡在 tokio、axum、reqwest 一整串:运行时要线程、网络栈和堆分配器,固件可能一样都没有;no_std 下连
String/Vec都默认不可用。可行做法是把 jackdaw 留在有操作系统的一侧跑,固件通过网络去调它——范式边界就是依赖清单的边界。