常用生态库 intermediate 30 分钟 更新 2026-06-15

clap:命令行参数解析

用 clap 的派生宏构建带校验的命令行接口。

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"] }