引言
今天开始「Rust 网络编程实践」的第一部分中的预备知识学习,并最终能编写出一个简单的命令行工具。以下材料是今天需要阅读的:
- The Cargo manifest format:了解 Cargo mainfest 文件格式;
- Cargo environment variables:了解 Cargo 环境变量;
- Rust API Guidelines: Documentation:了解 Rust 程序中的文档编写;
- Write a Good CLI Program:学习如何编写命令行程序。
接下来我们将依次完成上述材料学习,着重记录一些知识点,并在最后编写一个简单的命令行程序。
Cargo Manifest 文件格式
在使用 cargo
命令创建的项目中,都会存在一个 cargo.toml
文件,这也就是所谓的 manifest。一个简单的示例如下:
1 | [package] |
cargo.toml
是由多个部分配置组成的,主要包括如下几个部分(详细定义请参考文档):
- cargo-features: 实验性功能
[package]
: 包定义(如名称、版本、作者、Rust 编译器版本等)[[lib]]
: 库的设置[[bin]]
: 二进制文件设置[[example]]
: 示例文件设置[[test]]
: 测试设置[[bench]]
: 基准测试设置[dependencies]
: 依赖库列表[features]
: 条件编译功能[workspace]
: 工作空间定义
Cargo 环境变量
环境变量可以用来和 rustc
以及 Cargo 进行通信,可以在代码中通过 env!
宏或者在构建脚本中设置环境变量,这样可以控制构建行为。相关环境变量比较多,笔者在这里不多赘述了,等用到时再来查阅。建议把 Cargo environment variables 简单阅读下,留个印象。
Rust 程序文档编写
Rust API Guidelines 列出了一些设计和编写 Rust API 的建议,主要是由 Rust Lib 小组在构建 Rust 生态,编写标准库和一些其它 crates 期间总结而来。当然,这些建议并非一定需要遵守,不过既然来自于官方,那还是非常值得像笔者这样的 Rust 小白学习的。
今天主要学习其中关于文档编写的一些建议,其它内容建议抽空阅读下。关于文档编写的一些大建议梳理如下(详细示例还请参见文档 Rust API Guidelines: Documentation):
- crate 级别的文档应该是全面详细的,且包含使用示例;
- 所有公开的模块、trait、struct、enum、函数、方法、宏以及类型定义都需要提供使用示例;
- 文档中的实例应该使用
?
处理错误,而非try!
或者unwrap
; - 函数的文档中应该包括:函数功能描述、返回的错误(
# Errors
)、Panic 情况(# Panics
)以及一些安全使用的提示(# Safty
,比如std::ptr::read
这种unsafe
函数); - 文档中提到的类型应该关联到对应的文档(Link all the things);
Cargo.toml
中应该包含所有常用的元信息:- authors
- description
- license
- repository
- readme
- keywords
- categories
- crate 要设置正确的
#![doc(html_root_url = "https://docs.rs/CRATE/MAJOR.MINOR.PATCH")]
,尤其要注意这里的版本号要和Cargo.toml
中的version
保持一致; - 在发布日志(Release notes)中记录所有重大变更,尤其是不兼容变更需要清晰指出;
- 可以通过
#[doc(hidden)]
将一些没什么帮助的实现细节(如私有类型关联的方法实现等)从生成的文档中隐藏起来。
如何编写命令行(CLI)程序(Write a Good CLI Program)
Write a Good CLI Program 一文中讲解在使用 Rust 编写命令行程序的最佳实践,核心知识点包括:
- 使用
clap crates
编写可以接收复杂参数的命令行程序; - 使用
.env
编写应用配置,使用dotenv
读取环境变量; - 错误处理:
- panic 会导致程序直接退出,退出没有错误码,适合在脚本中使用;
- 函数返回
Result
,可以根据是否为Error
来决定后续操作; - 可以为自定义
Error
实现fmt::Display
trait,方便格式化输出错误消息。
- 使用
println!()
会打印到标准输出中,而使用eprintln!()
则是标准错误; - 使用
std::process::exit(1)
设置程序退出码(Exit Code)。
所谓的命令行程序(Command Line Interface, CLI)就是指在终端中运行的程序,没有图形界面。比如 ls
, ps
等。在 awesome-cli-apps 中收集了很多比较优质的命令行程序。
题外话:作者安利了一个叫作 exa 命令行程序,使用 Rust 语言实现,可以替代 ls
命令。笔者试用了一下,体验不错。
命令行程序长什么样
一般而言,命令行程序使用形式如下:
1 | $./program [args] [flags] [options] |
可以通过 -h
或 --help
查看更多帮助信息。
动手实践
大神 Linus Torvalds 曾言「Talk is cheap, show me the code」。那么,接下来就通过一个简单的示例把 Write a Good CLI Program 中提到的一些技巧实践一遍。
笔者曾经使用 Rust 实现过一个小工具 gic,它可以生成风格一致的 git commit message,还算实用。接下来实现一个简单版本 gitc
仅供参考,全部代码参见 talent-plan-projects/gitc。
首先使用 cargo 创建一个命令行应用:
1 | cargo new gitc |
应用目录结构如下:
1 | . |
定义命令行配置文件
1 | <--cli.yml--> |
注册 emoji 列表
为了方便扩展,将 emoji 相关信息放置在全局的列表中,扩展只需要新增列表项即可。这里使用 layzy_static
宏,他可以定义需要在运行时初始化的静态变量,用法类似在其它语言中定义全局变量。
1 | use lazy_static::lazy_static; |
构建命令行 App
clap 是一个功能丰富、工作高效的 Rust 命令行解析器,借助它可以非常轻松地创建命令行应用。gitc 在创建时,一部分命令行配置来自静态的 YAML 文件,另一部分则是基于上述可扩展的 emoji 列表配置。
1 | fn make_cli_app<'a, 'b>(yml: &'a Yaml) -> App<'a, 'b> { |
理解命令行参数并生成执行配置
为了便于理解,下面抽象了一个 CommitConfig
结构体,用于将 git commit
有关的特殊配置通过命令行参数解析到这里。具体定义如下:
1 |
|
执行 git commit 命令
由于需要通过 Shell 执行 git commit
命令,这里需要使用 std::process::Command
协助完成。核心代码如下:
1 | fn do_git_commit(c: &CommitConfig) -> Result<(), std::io::Error> { |
编写 main 函数
好啦,现在可以将上述过程串联起来,完成我们的 gitc 命令行工具:
1 | fn main() { |
完成后,使用 cargo build
可以生成可执行文件,运行效果如下:
小结
本节预备知识主要学习了与 Cargo 有关的环境变量作用、Cargo.toml
中的元信息构成以及编写规范的 API 文档的方法,最后通过编写 talent-plan-projects/gitc 掌握了如何编写一个简单的命令行程序以及一些最佳实践。