简介
实际的 Rust 程序通常会复用功能明确的库,而不是从头实现所有功能。已发布的 Rust 库称为 crate,你的软件包使用的 crate 称为依赖。安全地选择并添加依赖,是 Rust 开发中的常规工作。
在本实验中,你将为一个准备好的问候程序添加命令行解析器 clap。你会修改两处源代码,查看自动生成的帮助信息和参数验证结果,并了解 Cargo.toml 与 Cargo.lock 各自的作用。设置阶段已经预先下载了该依赖,因此首次构建会更快;但仍需要由你将依赖添加到实验项目中。
使用 Cargo 添加依赖
在本步骤中,你将把 clap 添加到准备好的软件包中,并查看 Cargo 自动修改的清单文件。
项目位于 /home/labex/project/hello-cli。运行 Cargo 命令前,先进入该目录:
cd /home/labex/project/hello-cli
使用 nano 打开软件包清单:
nano Cargo.toml
Cargo.toml 描述软件包及其直接依赖。空的 [dependencies] 表示该项目目前只使用 Rust 标准库。按 Ctrl+X 退出 nano,不要修改文件。
cargo add 命令可以安全地更新依赖表。添加 clap 版本 4.6.7,并启用它的 derive feature:
cargo add clap@4.6.7 --features derive
feature 用于启用 crate 的可选功能。这里,derive 会启用相关宏,使 Rust 结构体和枚举能够转换为命令行解析器。Cargo 使用 + 表示已启用的 feature,使用 - 表示未启用的 feature;未启用的 feature 不代表出错。
再次打开清单文件:
nano Cargo.toml
现在,依赖表中应包含类似下面的一行:
clap = { version = "4.6.7", features = ["derive"] }
这里的版本号表示兼容性要求。Cargo 可能会选择更新的兼容 4.x 版本,而最终选定的确切版本会单独记录在 Cargo.lock 中。按 Ctrl+X 关闭 nano。
派生命令行解析器
在本步骤中,你将使用 derive 宏和命令元数据,把 Cli 结构体连接到 clap。
打开准备好的源文件:
nano src/main.rs
你已经在入门课程中使用过 #[derive(Debug)]。derive 宏会根据类型的结构,请 crate 自动生成 trait 实现。将 struct Cli 上方的第一个 TODO 注释替换为下面两行:
#[derive(Parser)]
#[command(version, about = "Create a friendly greeting")]
#[derive(Parser)] 会生成参数解析行为。#[command(...)] 属性用于提供整个命令的信息:version 会从 Cargo.toml 读取软件包版本,about 则提供简短描述。
按 Ctrl+O 保存,按 Enter 确认,然后按 Ctrl+X 退出。先检查程序,不运行它:
cargo check
第一次检查会编译 clap 及其支持 crate,因此输出中可能会出现多行 Compiling 和 Checking。最后一行如果以 Finished 开头,表示依赖和生成的解析器已经一起编译成功。
现在让程序显示帮助信息。-- 用于分隔 Cargo 的选项和传递给程序本身的参数:
cargo run --quiet -- --help
输出中应包含描述、必需的 <NAME> 值,以及自动生成的帮助和版本选项:
Create a friendly greeting
Usage: hello-cli <NAME>
...
你只定义了数据结构;clap 根据该结构生成了一致的帮助信息和参数验证逻辑。
添加可选的重复次数选项
在本步骤中,你将添加一个类型化选项,并在一个小循环中使用解析后的值。
先使用一个位置参数运行命令:
cargo run --quiet -- Ada
Hello, Ada!
由于没有 #[arg(...)] 属性,name: String 会成为必需的位置参数。再次打开源文件:
nano src/main.rs
将 Cli 内部的第二个 TODO 注释替换为:
/// Number of greetings to print
#[arg(short, long, default_value_t = 1)]
times: u8,
文档注释会成为帮助文本。short 创建短选项 -t,long 创建长选项 --times,default_value_t = 1 则在未提供该选项时提供类型化的默认值。由于字段类型是 u8,clap 还会拒绝不是有效的 8 位无符号整数的值。
将最后一个 TODO 注释和单行 println! 替换为:
for _ in 0..cli.times {
println!("Hello, {}!", cli.name);
}
下划线表示循环计数本身不会被使用。保存并退出 nano,然后输出三次问候:
cargo run --quiet -- Ada --times 3
Hello, Ada!
Hello, Ada!
Hello, Ada!
再尝试一个无效值:
cargo run --quiet -- Ada --times many
该命令应执行失败。clap 会输出错误,说明 many 不是有效的 u8,并在 main 使用无效值之前以非零状态退出。
检查并复用锁定的依赖关系图
在本步骤中,你将检查 Cargo 解析出的依赖关系图,并验证无需网络访问也能通过锁定文件复现该依赖关系图。
clap 是你的直接依赖,但它自身还使用了其他支持 crate。显示解析后依赖关系图的第一层:
cargo tree --depth 1
确切的补丁版本可能比 Cargo.toml 中的兼容性要求更新。重要的是依赖关系的结构:
hello-cli v0.1.0 (...)
└── clap v4...
打开生成的锁定文件:
nano Cargo.lock
Cargo.lock 是自动生成的数据,通常不应手动编辑。它记录了 Cargo 为整个依赖关系图选择的确切版本和校验和。对于这个 CLI 这样的应用程序,应将锁定文件与项目一起保存,这样团队成员和自动化构建就能复用相同的依赖解析结果。按 Ctrl+X 关闭 nano。
现在要求同时使用现有锁定文件和本地 crate 缓存:
cargo check --locked --offline
--locked 会拒绝修改 Cargo.lock。--offline 会阻止网络访问。最后出现 Finished 行,说明项目可以直接使用已经下载的依赖关系图完成检查,而不会悄悄解析出不同的版本。
总结
你使用 Cargo 添加了直接 crate 依赖,启用了可选 feature,派生出了类型化的 clap 解析器,并观察了自动生成的帮助信息和参数验证。你还区分了 Cargo.toml 中的兼容性要求与 Cargo.lock 中的确切依赖关系图,并验证了锁定的依赖关系图可以在离线环境中正常工作。


