前言
都说 Rust 学起来比较难,学习曲线陡峭,我也是从入门到放弃了多次才逐渐上手,找到一点感觉。
我觉得一方面是因为最近 Rust 在前端基建领域很火,很多像我一样从 JS 这样的脚本语言入门的程序员,对于底层语言缺乏认知和底层思维。
另一方面是,Rust 的语法太多,很多小伙伴看完语法之后不知道做什么项目实战,过了一段时间又忘了,导致多次从入门到放弃。
所以我边学边写,打算写一系列的 Rust 实战教程,希望能对想入门 Rust 的同学起到帮助。
本次第一个系列是用 Rust 做一个命令行的 TODO List。
后续可能系列:
- Rust 和 actix 开发服务端
- Rust 写一个 Markdown parser
- Rust 开发 WebAssembly
- 等等
前置知识
本系列教程属于实战教程,不会教基础语法,虽然过程中也会稍微带一点点。所以希望你在开始之前,已经有了 Rust 的基础语法知识。
对于基础语法,我推荐你看一下 Rust 官方写的 《Rust Book》[1]。
同时,推荐边看边刷 rustlings[2]。
这样基本对于 Rust 的基础语法就有了一定的了解。
项目搭建
先看要实现的最终效果
我们给这个 cli 起名叫 rodo
,它拥有几个命令,分别是:
rodo add [content]
:添加一个 todorodo list
:列出所有的 todorodo remove [id]
:删除一个 todorodo info
:显示 rodo 的信息
以及通用的显示版本号和帮助信息的命令。
仓库地址
这个项目的代码我已经上传到 GitHub,欢迎大家 star 和 fork。
项目初始化
使用如下命令创建项目:
新建出来的项目目录结构如上图。执行 cargo run
会输出 Hello, world!
。
开始实战
获取命令行参数
第一步,我们先获取命令行参数,这里我们使用标准库中的 env::args()
方法获取命令行参数。
接下来,我们要对不同的命令做不同的处理,这里我们使用 match
语法。
添加如下代码:
执行 cargo run
,这里我们对不同的命令做了不同的处理,如果参数数量不对,会输出提示信息。
实现 todo 数据存储
既然是 todo list,那么数据需要有地方存储。这里我们写一个超简易的数据库,实际就是个文本文件,每一行是一个 todo,由 id 和内容组成,用逗号分隔。
新建一个 database.rs
文件。
先创建一个 Record
结构体,用来表示一条数据记录。
再创建一个 Database
结构体,用来表示整个数据库,它有一个 file 属性。
为 Database 实现一个 open 方法,参数接受一个文件名,返回一个 Database 实例
实现添加方法
先为 Database 实现一个 add_record
方法,用来添加一条记录。
此方法接受一个 Record
类型的参数,将其写入到文件中,使用 format!
宏将 Record
拼接成字符串,使用 writeln!
宏将字符串写入到文件中。
然后我们回到 main.rs
。
首先导入 database
模块,然后在 main
函数中创建一个 Database
实例。
使用 Database::open(".rododb")
创建了一个 Database
实例,它会打开一个名为 .rododb
的文件,如果文件不存在,会自动创建。
请注意,此时创建的文件是在项目根目录下,你会在项目根目录下看到一个 .rododb
文件。后续我们会更新文件的存储位置。
以上代码,当我们执行 cargo run add test
时,会调用 db
实例的 add_record
方法,将 Record
写入到 .rododb
文件中。
同时会在 .rododb
文件中看到一条记录。
实现列表方法
注意上面添加记录时,我写死了 id 为 1,实际上我们需要根据文件中已有 id 进行自增。
接下来我们实现列表方法,用来列出所有的 todo 并返回,并获取最大的 id。
回到 database.rs
。我们要实现一个读取文件的方法,将文件中的内容读取出来,而我们的数据字符串形式以行存储的,所以我们需要将每一行解析成 Record
。
这里实现一个 parse_record_line
方法,接受一个字符串,返回一个 Record
。
然后为 Database
实现一个 read_records
方法,用来读取文件中的所有记录,并返回一个 Vec<Record>
。
继续回到 main.rs
,调用我们刚刚实现的 read_records
方法,循环返回的 Vec 并打印输出。
OK! 没问题。
优化一下之前的添加逻辑:
Ok,这样就可以自增 id 了。
实现删除方法
接下来我们实现删除方法,删除方法接受一个 id,删除对应的 todo。
回到 database.rs
,为 Database
实现一个 remove_record
方法。
接下来根据参数 id 读取文件找到对应的行:
然后要做的操作就是,在源文件中删除这一行,然后将剩余的行写入到源文件中。
回到 main.rs
,调用 remove_record
方法。
我们来测试一下:
OK,没问题,删除成功。
小结
到这里,我们实现了 todo list 的基本功能,但是还有很多可以优化的地方,比如:
- 使用
clap
优化 CLI 的处理和交互 - 优化代码结构和错误处理
- db 文件现在存储在项目根目录,应该存储在用户目录下
由于篇幅有限,这些问题我们都将在下一篇文章中进行优化。
这个项目的代码我已经上传到 GitHub,本节内容在 part-1
分支,欢迎大家 star 和 fork,也可以贡献代码,对于本篇文章有任何疑问,欢迎在 GitHub 上提 issue。有错误的地方,欢迎指正。
liruifengv2333
,进群交流,抱团取暖。
- 新生代程序员群
- Astro 学习交流群
SayHub
,带来更多原创内容。