30 天拿下 Rust 之箱、包和模块
概述
Rust 语言使用模块系统来组织工程和代码。模块系统允许我们将相关的函数、类型、常量等组织在一起,形成一个逻辑上的单元。通过模块系统,我们可以隐藏实现细节,只暴露必要的接口,从而提高代码的可读性和可维护性。Rust 的模块系统还支持路径依赖和重导出等功能,使得代码的组织更加灵活和方便。
Rust 的模块系统中有三个非常重要的概念,分别是:箱(Crate)、包(Package)和模块(Module),下面逐一进行介绍。
箱(Crate)
箱,英文为 Crate,是 Rust 中的编译单元和构建单元,也是 Cargo 打包和分发的基本单位。Crate 可以是库(library crate),也可以是二进制程序(binary crate)。库 crate 包含了可以被其他 crate 使用的代码,二进制 crate 则包含了可以执行的程序。每个 crate 都有一个 crate root,它是编译器开始构建 crate 模块树的源文件。对于库 crate,crate root 通常是 src/lib.rs 文件;对于二进制 crate,crate root 通常是 src/main.rs 文件。
通过 crate,我们可以将代码进一步拆分成更小的、更易于管理和维护的单元。当在 Cargo 中创建一个新的项目时,实际上就是在创建一个 Crate。通过 cargo new my_crate 命令,Cargo 将为我们初始化一个新的 Crate 结构,其中包括:源码目录、测试文件、Cargo.toml 配置文件等。在 Rust 中,Crate 是编译时的概念,它指代的是编译后生成的一个单元,可以是一个库或者一个可执行程序。
包(Package)
包,英文为 Package,是 Cargo 用于组织和构建代码的基本单位。每个 Rust 项目都包含至少一个 Package,并通过名为 Cargo.toml 的配置文件来描述其属性和依赖关系。Package 的元数据存储在 Cargo.toml 文件中,这个文件包含了关于 Package 的基本信息,比如:名称、版本、作者、描述、许可证等。另外,Cargo.toml 还列出了 Package 的依赖项,这些依赖项是其他 Packages 或 Crates,它们会被 Cargo 自动下载和构建。
Package 通常包含源码目录,包括但不限于 src 目录下的 main.rs 或 lib.rs。如果项目更复杂,还可以有多个模块文件和子模块文件夹。一个 Package 可以包含一个或多个 Crates,但通常情况下,一个简单的 Package 会对应一个单一的 Crate。当通过 cargo build 命令构建项目时,最终输出的二进制文件或库文件就是这个 Crate。
模块(Module)
模块,英文为 Module,是用于在 crate 内部进行分层和封装的机制。模块内部又可以包含模块,从而形成一个树形结构,也称为模块树。每个 crate 会自动产生一个与当前 crate 同名的模块,作为这个树形结构的根节点。模块是元素(比如:函数、结构体、trait 等)的集合,是一种抽象的概念,而文件则是承载这个概念的实体。
在 Rust 中,创建新模块主要有以下三种方式。
1、在一个文件中创建内嵌模块。这可以通过直接使用 mod 关键字来实现,模块的内容会被包含在大括号内部。
2、独立的一个文件就是一个模块,文件名即是模块名。
3、一个文件夹也可以代表一个模块。在这种情况下,有两种方法可以实现:
(1)文件夹内部需要有一个名为 mod.rs 的文件,这个文件就是这个模块的入口。在 rustc 1.30 版本之前,这是唯一的方法。
(2)在文件夹同级目录里创建一个与模块(文件夹)同名的 rs 文件。在 rustc 1.30 版本之后,更建议使用这样的命名方式,以避免项目中存在大量同名的 mod.rs 文件。
模块树
模块树是一个逻辑上的分层结构,它反映了源代码文件的组织方式。每个 Rust 项目都可以看作一个模块树的根,其中包含零个或多个子模块。每个模块可以进一步包含其他的子模块,从而形成嵌套的层次结构。
在下面的示例模块树中,lib.rs 是 crate 的根模块,shapes 和 math 是它的子模块。circle 和 rectangle 是 shapes 的子模块,algebra 和 geometry 是 math 的子模块。shapes 之所以是模块,是因为 shapes 文件夹下有一个 mod.rs 文件。math 之所以是模块,是因为 math 同级目录下有一个同名的 math.rs 文件。在后面内容的介绍当中,我们也会用到这里的示例模块树。
模块路径
在 Rust 中,模块路径是用于唯一标识模块中定义的元素(比如:函数、结构体等)的字符串。模块路径由一系列由双冒号(::)分隔的标识符组成,从 crate 根开始,一直到指定的项,可以是绝对路径或相对路径。
绝对路径:以 crate::开始,表示从 crate 根开始的完整路径。在下面的示例代码中,crate::shapes::circle::Area 表示从 crate 根开始的 shapes 子模块、circle 子目录的 Area 函数。
相对路径:直接使用模块名称表示同级模块,或者相对于当前模块的子模块。有两个特殊的标识需要记住,self::表示当前模块,super::表示当前模块的父模块。
访问权限
在 Rust 中,访问权限是通过 pub 关键字来控制的。默认情况下,如果不加修饰符,模块中的成员访问权将是私有的。这意味着,它们只能在定义它们的模块内部被访问。如果想让其他模块能够访问某个成员,就需要在该模块和该成员前加上 pub 关键字来声明其为公开的。
访问权限主要有两种:一种是模块级的访问权限,另一种是成员级别的访问权限。
1、模块级的访问权限。公开模块可以在任何地方被访问,只要我们知道正确的路径。私有模块只能在与其平级的位置,或下级的位置被访问。也就是说,如果一个模块是私有的,那么只有在其同级模块或子模块中才能引用它。
2、成员级别的访问权限。使用 pub 关键字标记的成员是公开的,可以在其他模块中通过路径来访问。没有使用 pub 关键字标记的成员是私有的,只能在定义它们的模块内部访问。
除此之外,Rust 还提供了更细粒度的访问控制,允许我们指定一个成员仅在 crate 内部可见,或者仅在特定的模块及其子模块中可见。pub(crate)表示该成员在当前 crate 的任何地方都可见,但在外部 crate 中不可见。pub(in module)表示该成员在指定的模块及其子模块中可见,在其他模块不可见。
如果模块中定义了结构体,那么结构体本身以及它的字段默认都是私有的。如果希望结构体的某个字段能够被外部访问,则需要在结构体和该字段前均加上 pub 关键字。枚举类型则不同,只需要在枚举类型前加上 pub 关键字,而不需要在枚举成员前加上 pub 关键字。
使用 use
use 关键字用于导入模块或库中的元素(比如:函数、结构体等),以便在当前作用域中使用它们而无需使用完全限定的名称。use 语句通常放在文件的顶部,紧接在模块声明之后。
use 关键字的使用方式主要以下几种。
1、导入整个模块。可以使用 use 来导入整个模块,这样我们就可以直接使用该模块中公开的成员。
2、导入特定项。可以使用 use 来导入模块中的特定项,而不是整个模块。
3、重命名导入的项。如果导入的元素在当前作用域中已经存在同名项,或者想要使用不同的名称来引用它,我们可以使用 as 关键字来重命名。
4、使用通配符导入。使用*可以导入模块中所有公开的成员,但需要注意的是,过度使用通配符导入可能会导致名称冲突和不可预见的行为,因此通常建议明确导入你需要的元素。
5、多个 use 语句可以组合在一起,以提高便捷性和可读性。
总结
Rust 的模块系统是其代码组织管理的核心部分,它提供了一种方式来封装和组织代码,控制作用域和路径的私有性,以及导出公共接口。模块系统使得开发者能够构建大型、复杂的应用程序,同时保持代码的清晰性和可维护性。
版权声明: 本文为 InfoQ 作者【希望睿智】的原创文章。
原文链接:【http://xie.infoq.cn/article/539c6fe898a149ab746f27dfb】。文章转载请联系作者。
评论