今天,我想和大家分享我对 Rust 中的包(packages)、模块(modules)和箱(crates)的理解。Rust 的组织系统一开始让我觉得很难掌握,但经过一段时间的学习和实践,我终于逐渐理清了它们的关系。
让我们从基础开始,逐步深入!
箱(Crates)
箱(crate)是 Rust 程序的最小单元。例如,下面这段代码就是一个简单的 crate:
fn main() {
println!("I am a crate.");
}
在 Rust 中,一个重要的概念是 crate 根(crate root)。crate 根是编译器构建程序的起点。在上面的例子中,无论我们将文件命名为 something.rs 还是其他名字,这个文件都会被视为 crate 根。
Rust 中有两种类型的 crate:二进制 crate 和 库 crate。
- 二进制 crate 是独立的可执行文件,包含一个 main 函数,可以直接运行。
- 库 crate 是一组功能的集合,供其他 crate 使用。它没有 main 函数,无法独立运行。
在组织 Rust 程序时,一个常见的方式是将程序拆分为一个二进制 crate 和一个库 crate。二进制 crate(通常命名为 main.rs)包含可执行文件,而库 crate 则存储可复用的功能。二进制 crate 可以通过导入库 crate 的类型、方法等来使用它的功能。
包(Packages)
包(package)是一个或多个 crate 的集合,它们协同工作以提供某种功能。每个包都包含一个 Cargo.toml 文件,用于告诉 Rust 编译器如何构建其中的 crate。
- 一个包可以包含多个二进制 crate,但只能包含一个库 crate。
- 至少,一个包必须包含一个 crate(无论是二进制还是库)。
项目根目录下的 Cargo.toml 文件定义了一个包的存在。默认情况下,Cargo 会假定 src/main.rs 是二进制 crate 的根,而 src/lib.rs 是库 crate 的根。包的名称默认与二进制或库 crate 的名称一致,但可以在 Cargo.toml 中自定义。例如:
[[bin]]
name = "fun-with-nom"
path = "src/bin/httpd.rs"
[lib]
name = "fun_with_nom_lib"
path = "src/lib/lib.rs"
在组织 Rust 项目时,我发现将初始化和启动逻辑放在一个二进制 crate 中,而将核心功能放在一个库 crate 中非常有用。对于小型项目,这种结构可能显得繁琐,但对于大型代码库(例如 API),这种模块化的结构可以显著提高代码的可维护性。遵循这种结构让我在回顾旧项目时省了不少麻烦。
模块(Modules)
crate 可以进一步划分为模块(module),模块可以存在于单个文件中,也可以分布在多个文件中。模块的主要作用有两个:
- 组织代码:将相关代码分组为易于管理的单元。
- 控制可见性:模块中的代码默认是私有的,除非显式声明为公共(public)。
虽然可以将所有模块定义在一个文件中,但这种方式很快会变得难以管理。因此,将模块组织到单独的文件中是一种更好的做法,便于导航和维护。
路径(Paths)
Rust 编译器使用路径(path)来定位代码。路径类似于 Windows、Linux 或 macOS 中的文件系统路径,分为两种形式:
- 绝对路径:从 crate 根开始。对于外部 crate,路径以 crate 名称开头;对于当前 crate 的代码,路径以 crate 关键字开头。
- 相对路径:从当前模块开始,使用 self、super 或当前模块中的标识符。例如,super 表示父模块。
use 关键字
use 关键字可以将模块引入作用域,使其内容可以在程序的其他部分访问。这在避免重复书写路径时尤其有用。例如:
use serde::Deserialize;
如果我们在 Cargo.toml 的依赖项中添加了 serde crate(包括 derive 功能标志),这行代码会将 Deserialize 宏引入作用域,以便我们可以在自定义类型中使用它。
命名空间操作符(Namespace Operator)
Rust 的命名空间操作符 :: 通常与 use 关键字一起使用,用于访问模块中的项。例如:
use axum::{http::StatusCode, routing::get, response::IntoResponse};
在这里,我们引入了 axum Web 应用框架,并同时引入了以下具体依赖:
- http 模块中的 StatusCode 类型
- routing 模块中的 get 方法
- response 模块中的 IntoResponse 特性(trait)
当我们只需要模块中的某些内容时,可以将它们用 {} 包裹起来。
总结
一开始,我对如何有效使用 Rust 中的 crate、package 和 module 感到困惑。但随着实践的积累,这些概念逐渐变得清晰并融会贯通。我希望这篇文章能帮助那些面临类似挑战的读者。