Rust实现ToDo列表
基于Rust实现的终端ToDo学习项目。
初学者记录学习过程中遇到的各种问题和解决过程。
理解项目拆分任务
ToDo的主要实现功能有:
- 添加代办
- 修改代办
- 删除代办
- 完成代办
看似简单,上手发现很多细节有问题。
对于这些操作,都是基于任务或任务列表的操作,使用面向对象 开发。
我将项目拆分了两个对象
- 构建Tasks结构体,主要针对任务内容性的构建,记录任务内容,时间,重要程度,完成与否等信息,面向这些信息创建的功能对于修改这些信息的,有创建,修改,为了避免代码重复,创建了输入内容和等级的方法以及验证。
- 构建
ToDoManager管理结构体,记录任务ID和Tasks的HashMap,ID自增关联Tasks结构体做哈希表的键值对,对于少量数据的查询相当快,对于这个结构体,他实现的一般是新增,删除,列出等功能,相较宏观对任务进行的操作。
主要的功能和对象创造完成,下一步是实现这些功能,将这些方法穿起来。
流程开发
这一步根据项目流程逻辑,将面向对象创建的方法,通过面向过程进行串联。
- 添加代办,调用输入函数->Tasks下的创建函数->创建Tasks结构体->添加到
ToDoManager结构体下的哈希表中 - 修改代办,调取任务ID->调用输入函数(函数下存在传入可选修改,可适配添加和修改函数)->调哈希表->更新数据
- 删除代办,调任务ID->清理哈希表
将这些流程操作单独封装为函数,以便于进行跳转。
页面配置
不能使用函数剑的调用,会导致重复递归和嵌套。
使用状态机,利用枚举实现各个页面功能。跳转逻辑更加清晰。
rust
#[derive(PartialEq)]
enum PageState{
Main,
Add,
Modify,
Delete,
Exit,
}
......
while default_state != PageState::Exit{
default_state =match default_state{
PageState::Main => todo_main(&mut todo, terminal_width),
PageState::Add => todo_add(&mut todo, terminal_width),
PageState::Delete => todo_delete(&mut todo, terminal_width),
PageState::Modify => todo_modify(&mut todo, terminal_width),
PageState::Exit => PageState::Exit,
};
}
这些不同函数中,输入不同字符进行返回不同枚举类型(例如输入1添加任务),返回枚举后重新while判断。
细节处理
- 格式化打印,
terminal_size::{terminal_size,Width};获取命令行宽度。 - 清除命令行,清理其他内容
println!("\x1B[2J\x1B[1;1H");。 - 不同等级颜色高光,
"\x1b[91m"和"\x1b[0m"中间夹着的地方进行改变颜色,91亮红。 - 时间获取,先通过标准库获取时间戳,
chrono::{DateTime, Local};对时间戳进行转换。 - 退出/跳转,获取
q字母进行返回PageState::Main或退出。 - 重要等级排列,遍历收集到
vec存储,降序排列.sort_by(|a, b| b.1.level.cmp(&a.1.level));,使用依然是引用,但足以,主要以完成目标为主,过早优化是大忌。
待开发部分:
- 数据持久化
- 标记完成和完成任务的记录(终端的
ToDo列表实用性并不大,主要理解项目的逻辑)

悟道
项目的开发经历了数小时,查阅标准库,代码补全,问AI帮助了我很多,帮我选择库,帮我查出一些细节的实现。
AI的促进学习很大的体现,极大程度辅助了开发,各种库和知识都是在学习和写代码中逐步记忆,而AI的出现可以帮助我们去节省寻找实现方案的时间。只有真带着项目学习,才能学习到教程中没有涉及的部分。
理论->实践->理论 的学习过程,才能真正帮助深刻学习。
写出代码并不是很重要,重要的是理解项目逻辑,用更加清晰的逻辑拆分项目。
参考代码实现
后续都将上传我的Blog
ini
实现库
[dependencies]
terminal_size = "0.3"
chrono = "0.4"
rust
use std::time::SystemTime;
use std::collections::HashMap;
use std::io;
use terminal_size::{terminal_size,Width};
use chrono::{DateTime, Local};
#[derive(PartialEq)]
enum PageState{
Main,
Add,
Modify,
Delete,
Exit,
}
fn format_time(time: &SystemTime) -> String {
let datetime: DateTime<Local> = time.clone().into();
datetime.format("%Y-%m-%d %H:%M").to_string()
}
fn get_color(level: u8) -> String{
let color = match level{
1 => "\x1b[97m",
2 => "\x1b[94m",
3 => "\x1b[93m",
4 => "\x1b[95m",
5 => "\x1b[91m",
_ => "\x1b[0m",
};
color.to_string()
}
struct Tasks{
name: String,
time: SystemTime,
finish: bool,
level: u8,
}
impl Tasks{
fn new(name: String, level: u8) -> Result<Tasks, String>{
fn get_time() -> SystemTime{
SystemTime::now()
}
Ok(Tasks{
name,
time: get_time(),
finish: false,
level,
})
}
fn modify(&mut self, name: String, level: u8){
self.name = name;
self.level = level;
}
// fn mark_done(&mut self) -> bool{
// self.finish = true;
// self.finish
// }
fn input_name(may_name: Option<String>) -> String{
let mut name = String::new();
io::stdin().read_line(&mut name).expect("读取失败");
if name.trim().is_empty(){
name = may_name.unwrap_or("".to_string());
}
name.trim().to_string()
}
fn input_level(may_level: Option<u8>) ->u8{
let mut level:u8 = 0;
while !(level >= 1 && level <= 5){
println!("等级必须在 1-5 之间");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("读取失败");
if input.is_empty(){
level = may_level.unwrap_or(0);
break;
}
level = input.trim().parse().unwrap_or(0);
}
return level;
}
}
struct TodoManager{
id: u8,
tasks: HashMap<u8,Tasks>,
}
impl TodoManager{
fn new() -> TodoManager{
TodoManager{
id: 1,
tasks: HashMap::new(),
}
}
fn add_task(&mut self, name: String, level: u8){
let task = Tasks::new(name, level);
self.tasks.insert(self.id, task.unwrap());
self.id += 1;
}
fn delete_task(&mut self, id: u8){
self.tasks.remove(&id);
}
fn list_tasks(&self){
let mut tasks_vec: Vec<_> = self.tasks.iter().collect();
tasks_vec.sort_by(|a, b| b.1.level.cmp(&a.1.level));
for (id, task) in tasks_vec{
let color = get_color(task.level);
let time = format_time(&task.time);
println!("{}: {}任务:{} 等级:{} 时间:{} 是否完成: {}{}", id, color, task.name, task.level, time, task.finish, "\x1b[0m");
}
}
}
fn todo_main(todo: &mut TodoManager , terminal_width: usize) -> PageState{
println!("{}", "=".repeat(terminal_width));
println!("{:^width$}", "ToDo List", width = terminal_width);
println!("目前待办列表为:");
todo.list_tasks();
println!("{}", "=".repeat(terminal_width));
let col_width = terminal_width / 4;
println!("{:<col_width$}{:<col_width$}{:<col_width$}{:<col_width$}",
"1. 添加任务", "2. 修改任务", "3. 删除任务", "q. 退出",
col_width = col_width);
let mut state = String::new();
io::stdin().read_line(&mut state).expect("读取失败");
match state.trim(){
"1" => PageState::Add,
"2" => PageState::Modify,
"3" => PageState::Delete,
"q" => PageState::Exit,
_ => {
println!("输入无效");
PageState::Main
}
}
}
fn todo_add(todo: &mut TodoManager, terminal_width: usize) -> PageState{
println!("{}", "=".repeat(terminal_width));
println!("{:^width$}", "ToDo List", width = terminal_width);
println!("添加任务内容");
let name = Tasks::input_name(None);
println!("添加任务等级");
let level = Tasks::input_level(None);
todo.add_task(name, level);
PageState::Main
}
fn todo_modify(todo: &mut TodoManager, terminal_width: usize) -> PageState{
println!("{}", "=".repeat(terminal_width));
println!("{:^width$}", "ToDo List", width = terminal_width);
println!("输入要修改的任务ID");
todo.list_tasks();
println!("{}", "=".repeat(terminal_width));
println!("输入q返回上一级");
let mut inputid: u8 = 0;
while !todo.tasks.contains_key(&inputid){
let mut tryinputid = String::new();
io::stdin().read_line(&mut tryinputid).expect("读取失败");
if tryinputid.trim() == "q"{
return PageState::Main;
}
inputid = tryinputid.trim().parse().unwrap_or(0);
}
println!("输入改动的任务名称");
let name = Tasks::input_name(Some(todo.tasks.get(&inputid).unwrap().name.clone()));
println!("输入改动的任务等级");
let level = Tasks::input_level(Some(todo.tasks.get(&inputid).unwrap().level));
todo.tasks.get_mut(&inputid).unwrap().modify(name, level);
PageState::Main
}
fn todo_delete(todo: &mut TodoManager, terminal_width: usize) -> PageState{
println!("{}", "=".repeat(terminal_width));
println!("{:^width$}", "ToDo List", width = terminal_width);
println!("输入要删除的任务ID");
todo.list_tasks();
println!("{}", "=".repeat(terminal_width));
println!("输入q返回上一级");
let mut id: u8 = 0;
while !todo.tasks.contains_key(&id){
let mut tryid = String::new();
io::stdin().read_line(&mut tryid).expect("读取失败");
if tryid.trim() == "q"{
return PageState::Main;
}
id = tryid.trim().parse().unwrap_or(0);
}
todo.delete_task(id);
PageState::Main
}
fn get_terminal_width() -> usize{
terminal_size().map(|(Width(w), _)| w as usize).unwrap_or(80)
}
fn main(){
let terminal_width = get_terminal_width();
let mut todo = TodoManager::new();
todo.add_task("学习Rust".to_string(), 5);
todo.add_task("学习AI".to_string(), 4);
let mut default_state: PageState = PageState::Main;
println!("\x1B[2J\x1B[1;1H");
while default_state != PageState::Exit{
default_state =match default_state{
PageState::Main => todo_main(&mut todo, terminal_width),
PageState::Add => todo_add(&mut todo, terminal_width),
PageState::Delete => todo_delete(&mut todo, terminal_width),
PageState::Modify => todo_modify(&mut todo, terminal_width),
PageState::Exit => PageState::Exit,
};
println!("\x1B[2J\x1B[1;1H");
}
}