命令行参数:clap
为什么用 clap?
clap 是 Rust 生态中最流行的命令行参数解析库,被 ripgrep(rg)、bat、cargo 等知名工具使用。它能自动生成帮助文档、验证参数类型、支持子命令,并且使用声明式宏或 derive 宏,代码非常简洁。
[dependencies]
clap = { version = "4", features = ["derive"] }
use clap::Parser;
/// 一个简单的文件处理工具
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// 要处理的文件路径
path: std::path::PathBuf,
/// 显示行号
#[arg(short = 'n', long)]
line_numbers: bool,
/// 过滤包含此字符串的行
#[arg(short, long)]
filter: Option<String>,
/// 最多显示的行数
#[arg(short, long, default_value_t = 100)]
max_lines: usize,
}
fn main() {
let cli = Cli::parse();
println!("处理文件: {:?}", cli.path);
println!("行号: {}, 过滤: {:?}, 最多行: {}",
cli.line_numbers, cli.filter, cli.max_lines);
}
子命令(Subcommands)
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "mytool")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// 添加条目
Add {
/// 条目内容
content: String,
},
/// 列出所有条目
List {
#[arg(short, long)]
verbose: bool,
},
/// 删除条目
Remove { id: u32 },
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Add { content } => println!("添加: {}", content),
Commands::List { verbose } => println!("列出(详细: {})", verbose),
Commands::Remove { id } => println!("删除 #{}", id),
}
}
文件 I/O
读写文件的多种方式
use std::fs;
use std::io::{self, BufRead, Write};
use std::path::Path;
fn file_examples() -> io::Result<()> {
// 一次性读取全部内容
let content = fs::read_to_string("input.txt")?;
println!("文件内容:\n{}", content);
// 读取为字节
let bytes = fs::read("binary.bin")?;
println!("字节数: {}", bytes.len());
// 一次性写入(覆盖)
fs::write("output.txt", "Hello, Rust!\n")?;
// 追加写入
use fs::OpenOptions;
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open("log.txt")?;
writeln!(file, "新的日志条目")?;
Ok(())
}
// 逐行读取大文件(内存高效)
fn read_lines(filename: &str) -> io::Result<Vec<String>> {
let file = fs::File::open(filename)?;
let reader = io::BufReader::new(file);
reader.lines().collect()
}
路径操作
use std::path::{Path, PathBuf};
fn path_examples() {
let path = Path::new("/home/user/projects/rust/src/main.rs");
println!("文件名: {:?}", path.file_name()); // "main.rs"
println!("扩展名: {:?}", path.extension()); // "rs"
println!("父目录: {:?}", path.parent()); // .../src
println!("是否存在: {}", path.exists());
println!("是否是文件: {}", path.is_file());
// PathBuf:可变路径
let mut buf = PathBuf::from("/home/user");
buf.push("projects");
buf.push("rust");
println!("{:?}", buf); // /home/user/projects/rust
// 遍历目录
if let Ok(entries) = std::fs::read_dir(".") {
for entry in entries.flatten() {
println!("{:?}", entry.file_name());
}
}
}
序列化:serde + JSON
serde:Rust 序列化的标准
serde 是 Rust 生态中几乎无处不在的序列化/反序列化框架,支持 JSON、TOML、YAML、MessagePack 等数十种格式,且通过过程宏零样板代码地实现。
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
use serde::{Deserialize, Serialize};
use serde_json;
#[derive(Serialize, Deserialize, Debug, Clone)]
struct User {
id: u32,
name: String,
email: String,
#[serde(default)] // 缺失时使用默认值
active: bool,
#[serde(skip_serializing_if = "Option::is_none")] // 为 None 时不序列化
avatar: Option<String>,
#[serde(rename = "createdAt")] // JSON 中用驼峰命名
created_at: String,
}
fn main() -> Result<(), serde_json::Error> {
let user = User {
id: 1,
name: String::from("Alice"),
email: String::from("alice@example.com"),
active: true,
avatar: None,
created_at: String::from("2024-01-01"),
};
// 序列化为 JSON 字符串
let json = serde_json::to_string_pretty(&user)?;
println!({}, json);
// 反序列化:JSON 字符串 → Rust 结构体
let json_str = r#"{"id":2,"name":"Bob","email":"bob@example.com","createdAt":"2024-02-01"}"#;
let user2: User = serde_json::from_str(json_str)?;
println!("{:?}", user2);
// 序列化为文件
let file = std::fs::File::create("user.json")
.map_err(|e| serde_json::Error::io(e))?;
serde_json::to_writer_pretty(file, &user)?;
Ok(())
}
处理动态 JSON
use serde_json::{Value, json};
fn dynamic_json() {
// json! 宏创建动态 JSON 值
let data = json!({
"name": "Rust",
"version": 2024,
"features": ["safety", "speed", "concurrency"]
});
// 动态访问(注意:可能返回 None)
if let Some(name) = data["name"].as_str() {
println!("语言: {}", name);
}
// 遍历数组
if let Some(features) = data["features"].as_array() {
for f in features {
println!("特性: {}", f);
}
}
}
完整 CLI 工具实战
构建一个 TODO 管理工具
[dependencies]
clap = { version = "4", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
anyhow = "1"
use anyhow::Result;
use clap::{Parser, Subcommand};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::PathBuf;
const DATA_FILE: &str = "todos.json";
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Todo {
id: u32,
title: String,
done: bool,
}
#[derive(Parser)]
#[command(name = "todo", about = "TODO 任务管理工具")]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// 添加新任务
Add { title: String },
/// 列出所有任务
List,
/// 标记任务完成
Done { id: u32 },
/// 删除任务
Remove { id: u32 },
}
fn load_todos() -> Result<Vec<Todo>> {
if !PathBuf::from(DATA_FILE).exists() {
return Ok(vec![]);
}
let content = fs::read_to_string(DATA_FILE)?;
Ok(serde_json::from_str(&content)?)
}
fn save_todos(todos: &[Todo]) -> Result<()> {
fs::write(DATA_FILE, serde_json::to_string_pretty(todos)?)?;
Ok(())
}
fn main() -> Result<()> {
let cli = Cli::parse();
let mut todos = load_todos()?;
match cli.command {
Command::Add { title } => {
let id = todos.iter().map(|t| t.id).max().unwrap_or(0) + 1;
todos.push(Todo { id, title: title.clone(), done: false });
save_todos(&todos)?;
println!("✓ 已添加 #{}: {}", id, title);
}
Command::List => {
if todos.is_empty() {
println!("暂无任务");
} else {
for t in &todos {
let mark = if t.done { "✓" } else { "○" };
println!("[{}] #{}: {}", mark, t.id, t.title);
}
}
}
Command::Done { id } => {
if let Some(t) = todos.iter_mut().find(|t| t.id == id) {
t.done = true;
save_todos(&todos)?;
println!("✓ 已完成 #{}", id);
} else {
println!("未找到 #{}", id);
}
}
Command::Remove { id } => {
let before = todos.len();
todos.retain(|t| t.id != id);
if todos.len() < before {
save_todos(&todos)?;
println!("✓ 已删除 #{}", id);
} else {
println!("未找到 #{}", id);
}
}
}
Ok(())
}
Rust CLI 工具的性能优势
Rust 编写的 CLI 工具启动时间通常在毫秒级别,内存占用极低。相比之下,Python 工具启动时需要加载解释器(通常需要数十毫秒到数百毫秒),Java/Node.js 工具需要 JVM/V8 初始化(数百毫秒甚至更长)。这使得 Rust 成为编写高频调用的系统工具的首选语言。