Rust实战:200行代码写一个命令行TODO工具
Rust实战:200行代码写一个命令行TODO工具
🎏:你只管努力,剩下的交给时间
🏠 :小破站
Rust实战:200行代码写一个命令行TODO工具
前言
上一篇我们把Rust环境搭好了,也跑通了Hello World。但说实话,那还是太简单了。学编程语言,最好的方式就是做点实际的东西出来。
所以这篇文章,我们来写一个真正能用的工具:命令行TODO管理器。
为什么选这个项目?因为它:
- 功能明确:增删改查,逻辑清晰
- 代码量适中:200行左右,不会看晕
- 能展示Rust特性:所有权、借用、模式匹配、错误处理…核心概念都能用上
- 做完能用:真的可以当日常工具用
整个开发过程大概1小时,跟着做完,你对Rust的理解会上一个台阶。
项目需求
先明确一下要做什么功能:
- 添加任务:
todo add "学习Rust" - 列出任务:
todo list- 显示所有任务 - 完成任务:
todo done 1- 把ID为1的任务标记为完成 - 删除任务:
todo remove 1- 删除指定任务 - 清空已完成:
todo clear- 一键清除所有已完成的任务
数据要持久化保存在本地文件(用JSON格式),这样关掉终端再打开,数据还在。
第一步:创建项目
老规矩,用cargo创建项目:
cargo new rust_todo
cd rust_todo
ls -la

可以看到cargo帮我们生成了完整的项目结构:
.git/- 已经初始化了git仓库Cargo.toml- 项目配置文件src/main.rs- 源代码入口.gitignore- 已经配置好忽略规则
Cargo真的很贴心,连git都帮你弄好了。
第二步:配置依赖
我们需要三个库:
- serde - 序列化和反序列化(把Rust对象转成JSON)
- serde_json - JSON处理
- chrono - 时间处理(记录任务创建时间)
编辑 Cargo.toml,在 [dependencies] 下添加:
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = "0.4"

为什么需要这些库?
serde的derive特性可以自动生成序列化代码,不用手写serde_json用来读写JSON文件chrono让时间处理更方便
第三步:编写代码
这是整个项目的核心。完整代码大概200行,我会分模块讲解。
代码整体结构

从截图可以看到,代码有212行,主要分为几个部分:
- 数据结构定义
- 文件操作
- 核心功能实现
- 命令行参数解析
代码组织得很清晰,这也是Rust的优点之一。
数据结构定义

#[derive(Debug, Serialize, Deserialize, Clone)]
struct Todo {
id: u32,
content: String,
completed: bool,
created_at: String,
}
impl Todo {
fn new(id: u32, content: String) -> Self {
use chrono::Local;
let now = Local::now().format("%Y-%m-%d %H:%M").to_string();
Todo {
id,
content,
completed: false,
created_at: now,
}
}
}
这里有几个Rust特色的东西:
-
#[derive(...)]- 这是Rust的过程宏,自动实现traitDebug- 可以用{:?}打印Serialize和Deserialize- 可以转成JSONClone- 可以复制
-
impl块 - 给结构体添加方法fn new()是构造函数-> Self表示返回Todo类型Self是Todo的别名
-
字段默认不可变 -
completed: false在创建时设置
文件操作

fn get_todos_file() -> PathBuf {
let home = env::var("HOME").expect("无法获取HOME目录");
PathBuf::from(home).join(".todos.json")
}
fn load_todos() -> Vec<Todo> {
let file_path = get_todos_file();
if !file_path.exists() {
return Vec::new();
}
let data = fs::read_to_string(&file_path).expect("读取文件失败");
serde_json::from_str(&data).unwrap_or_else(|_| Vec::new())
}
fn save_todos(todos: &Vec<Todo>) {
let file_path = get_todos_file();
let json = serde_json::to_string_pretty(todos).expect("序列化失败");
fs::write(&file_path, json).expect("写入文件失败");
}
这段代码展示了Rust的几个重要概念:
-
PathBuf- 可变的路径类型.join()拼接路径- 跨平台兼容
-
借用
&-save_todos(todos: &Vec<Todo>)&表示借用,不转移所有权- 函数执行完后,调用者还能继续用
todos
-
错误处理
expect()- 如果出错就panic并显示消息unwrap_or_else()- 出错时提供默认值
为什么 save_todos 要用借用? 因为保存完后,我们可能还要继续操作这个列表,不能把所有权转移走。这就是Rust所有权系统的优雅之处。
核心功能

这里实现了 add_todo 和 list_todos 函数。
添加任务的逻辑:
- 加载现有任务
- 计算新ID(取最大ID + 1)
- 创建新任务
- 添加到列表
- 保存到文件
let new_id = todos.iter().map(|t| t.id).max().unwrap_or(0) + 1;
这行代码很有Rust风格:
.iter()- 创建迭代器.map(|t| t.id)- 闭包,提取每个任务的ID.max()- 找最大值,返回Option<u32>.unwrap_or(0)- 如果没有任务(None),返回0
列出任务的逻辑:
- 遍历所有任务
- 已完成的显示
[✓]和删除线 - 未完成的显示
[ ] - 最后显示统计信息
这里用到了ANSI转义序列:
format!("\x1b[9m{}\x1b[0m", todo.content) // 删除线效果
\x1b[9m 是删除线,\x1b[0m 是重置样式。这让已完成的任务看起来更明显。
第四步:编译项目
代码写完后,第一次编译:
cargo build

可以看到cargo在下载和编译依赖:
proc-macro2、quote- serde需要的宏相关库serde、serde_json- 我们配置的依赖chrono- 时间处理库
第一次编译会慢一点,因为要下载和编译所有依赖。 后续编译就快了,cargo会缓存编译结果。
编译成功后,可执行文件在 target/debug/rust_todo。
第五步:测试功能
查看帮助信息
cargo run -- help

注意这里的 --,它告诉cargo:后面的参数是给我们的程序的,不是给cargo的。
帮助信息显示得很清楚:
- 5个命令的用法
- 每个命令的说明
- 使用示例
添加任务
连续添加5个任务:
cargo run -- add "学习Rust所有权系统"
cargo run -- add "写TODO工具"
cargo run -- add "阅读Rust官方文档"
cargo run -- add "练习Rust错误处理"
cargo run -- add "完成第二篇文章"

每次添加都显示"✅ 任务已添加!",说明功能正常。
有个细节:后面几次编译都显示 Finished in 0.01s,说明cargo检测到代码没变,直接用缓存了。这就是增量编译的好处。
列出任务
cargo run -- list

显示结果很清晰:
- 每个任务都有ID、内容、创建时间
- 前面的
[ ]表示未完成 - 底部统计:总计5个任务,已完成0个,待办5个
Rust的字符串处理很方便,用 "=".repeat(60) 就能画出分割线。
完成任务
把ID为1和3的任务标记为完成:
cargo run -- done 1
cargo run -- done 3
cargo run -- list

再次查看列表,可以看到:
- ID为1和3的任务前面变成了
[✓] - 任务内容有删除线效果(截图可能看不太清楚,但在终端里是有的)
- 统计更新:已完成2个,待办3个
这里体现了Rust的可变性控制:
if let Some(todo) = todos.iter_mut().find(|t| t.id == id) {
todo.completed = true;
}
.iter_mut()- 可变迭代器,可以修改元素if let Some(todo)- 优雅的模式匹配
删除任务
删除ID为4的任务:
cargo run -- remove 4
cargo run -- list

删除后,列表从5个任务变成4个。ID为4的"练习Rust错误处理"消失了。
删除用到了 .retain() 方法:
todos.retain(|t| t.id != id);
这行代码的意思是:保留所有ID不等于指定ID的任务。简洁又清晰。
清除已完成任务
cargo run -- clear
cargo run -- list

执行 clear 命令后,显示"🗑️ 已清除 2 个已完成任务"。
再看列表,只剩2个待办任务了:
- ID为2的"写TODO工具"
- ID为5的"完成第二篇文章"
注意ID不连续,这是正常的。因为ID为1和3被清除了,ID为4被删除了。
查看数据文件
cat ~/.todos.json

数据文件是pretty print的JSON格式,可读性很好:
[
{
"id": 2,
"content": "写TODO工具",
"completed": false,
"created_at": "2025-11-01 16:17"
},
{
"id": 5,
"content": "完成第二篇文章",
"completed": false,
"created_at": "2025-11-01 16:17"
}
]
这就是 serde_json::to_string_pretty() 的效果,比压缩的JSON好读多了。
测试错误处理
试试各种错误情况:
cargo run -- done 999 # 不存在的ID
cargo run -- xyz # 无效命令
cargo run -- add # 缺少参数

每种错误都有友好的提示:
- 不存在的ID:“❌ 找不到ID为 999 的任务”
- 无效命令:“❌ 未知命令: xyz” 并显示帮助信息
- 缺少参数:“❌ 请提供任务内容” 并给出示例
良好的错误提示是优秀工具的标志。 Rust让我们很容易做到这一点。
代码中的Rust特性
通过这个项目,我们实际用到了很多Rust核心特性:
1. 所有权和借用
fn save_todos(todos: &Vec<Todo>) // 借用,不转移所有权
如果写成 todos: Vec<Todo>(不加&),调用后外面就不能再用这个变量了。加了 & 就只是借用,用完还回去。
2. 模式匹配
match args[1].as_str() {
"add" => { ... }
"list" | "ls" => { ... }
_ => { ... }
}
Rust的 match 必须覆盖所有情况,编译器会检查。这避免了很多bug。
3. Option和Result
todos.iter().map(|t| t.id).max() // 返回 Option<u32>
fs::read_to_string(&file_path) // 返回 Result<String, Error>
Rust没有null,用 Option 表示可能没有值。用 Result 表示可能出错。这让错误处理更明确。
4. 迭代器
todos.iter() // 不可变迭代
todos.iter_mut() // 可变迭代
todos.iter().filter() // 过滤
todos.retain() // 保留符合条件的
Rust的迭代器是零成本抽象,写起来优雅,性能和手写循环一样。
5. 闭包
.map(|t| t.id) // 提取字段
.find(|t| t.id == id) // 查找
.unwrap_or_else(|_| Vec::new()) // 提供默认值
闭包语法简洁,|参数| 是参数列表,后面是函数体。
6. trait和derive
#[derive(Debug, Serialize, Deserialize, Clone)]
通过 derive 自动实现常用trait,不用手写一堆模板代码。
项目总结
这个TODO工具虽然简单,但麻雀虽小五脏俱全:
功能完整:
- ✅ CRUD操作(增删改查)
- ✅ 数据持久化
- ✅ 命令行界面
- ✅ 错误处理
- ✅ 帮助信息
代码质量:
- ✅ 结构清晰,模块分明
- ✅ 错误提示友好
- ✅ 注释合理
- ✅ 符合Rust习惯
Rust特性应用:
- ✅ 所有权系统保证内存安全
- ✅ 模式匹配让逻辑清晰
- ✅ 错误处理显式化
- ✅ 迭代器优雅高效
大约200行代码,实现了一个真正可用的工具。 这就是Rust的魅力:在保证安全性的同时,代码还能写得很简洁。
彩蛋
获取完整代码:https://minio.acowbo.fun/file/rust_todo.zip
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐



所有评论(0)