clap:命令行参数解析
你会学到什么
clap用派生宏把结构体变成命令行解析器。#[arg(...)]声明短选项、长选项、默认值。- 自动获得
--help、--version和参数校验。
最小示例
use clap::Parser;
#[derive(Parser)]
struct Args {
#[arg(short, long)]
name: String,
#[arg(short, long, default_value_t = 1)]
times: u32,
}
运行代码
cd examples
cargo run -p rt_36_clap -- --name Rust --times 2
cargo test -p rt_36_clap
依赖:
[dependencies]
clap = { version = "4", features = ["derive"] }
代码讲解
Args::parse() 从真实命令行读取参数;在测试里用 try_parse_from 传入一个参数数组,无需启动进程:
let args = Args::try_parse_from(["greet", "--name", "Rust", "--times", "2"]).unwrap();
#[arg(short, long)] 同时生成 -n 和 --name,default_value_t 提供默认值。缺少必填参数或类型不符时,clap 会自动报错并打印用法,这也是为什么 requires_name 测试里 try_parse_from(["greet"]) 返回 Err。
注意命令行里 -- 之后才是传给程序的参数:cargo run -p ... -- --name Rust。
常见错误
把业务逻辑和解析混在一起,难以测试。示例把渲染逻辑抽成 render(&args),这样不依赖命令行也能单测。
练习
- 加一个
--shout布尔开关,为 true 时把问候转成大写。 - 用 clap 的子命令(
Subcommand)实现add/list两个动作。
小结
clap 用声明式的派生宏生成健壮的命令行接口,把解析、校验、帮助文档都自动化。
下一步
接下来进入 Web 与数据库阶段,用 axum 和 SQLite 构建服务。
完整示例代码
下面是 examples/36_clap/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。
examples/36_clap/src/main.rs
//! clap derive:声明式命令行参数解析。
use clap::Parser;
#[derive(Parser, Debug, PartialEq)]
#[command(name = "greet", about = "打招呼的示例 CLI")]
struct Args {
/// 要问候的名字。
#[arg(short, long)]
name: String,
/// 重复次数。
#[arg(short, long, default_value_t = 1)]
times: u32,
}
fn render(args: &Args) -> String {
(0..args.times)
.map(|_| format!("Hello, {}!", args.name))
.collect::<Vec<_>>()
.join("\n")
}
fn main() {
let args = Args::parse();
println!("{}", render(&args));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parses_args() {
let args = Args::try_parse_from(["greet", "--name", "Rust", "--times", "2"]).unwrap();
assert_eq!(
args,
Args {
name: "Rust".to_string(),
times: 2,
}
);
}
#[test]
fn uses_default_times() {
let args = Args::try_parse_from(["greet", "--name", "Rust"]).unwrap();
assert_eq!(args.times, 1);
}
#[test]
fn renders_repeated_greeting() {
let args = Args {
name: "Rust".to_string(),
times: 2,
};
assert_eq!(render(&args), "Hello, Rust!\nHello, Rust!");
}
#[test]
fn requires_name() {
assert!(Args::try_parse_from(["greet"]).is_err());
}
} examples/36_clap/Cargo.toml
[package]
name = "rt_36_clap"
version.workspace = true
edition.workspace = true
publish.workspace = true
[dependencies]
clap = { version = "4", features = ["derive"] }