rust 包模块组织结构详解
作者:i-neojos
一个包(package
)可以拥有多个二进制单元包及一个可选的库单元包。随着包内代码规模的增长,你还可以将代码拆分到独立的单元包(crate
)中,并将它作为外部依赖进行引用。
RUST
提供了一系列的功能来帮助我们管理代码,包括决定哪些细节是暴露的、哪些细节是私有的,以及不同的作用域的命名管理。这些功能有时被统称为模块系统(module system
),它们包括:
- 包(package):一个用于构建、测试并分享单元包的Cargo功能
- 单元包(crate):一个用于生成库或可执行文件的树形模块结构
- 模块(module)及use关键字:它们被用于控制文件结构、作用域及路径的私有性
- 路径(path):一种用于命名条目的方法,这些条目包括结构体、函数和模块等
有几条规则决定了包可以包含哪些东西:首先,一个包中最多只能拥有一个库单元包。其次,包可以拥有多个二进制单元包。最后,包内必须存在至少一个单元包(库单元包或二进制单元包)。
cargo new my-project
当我们执行这条命令时,Cargo
会生成一个包并创建相应的Cargo.toml
文件。Cargo
会默认将src/main.rs
视作一个二进制单元包的根节点,这个二进制单元包与包拥有相同的名字。同样地,假设包的目录中包含文件src/lib.rs
,Cargo
也会自动将其视作与包同名的库单元包的根节点。
最初生产的包只包含源文件src/main.rs
,这也意味着只包含一个名为my-project
的二进制单元包。而假设包中同时存在src/main.rs
及src/lib.rs
,那么其中就会分别存在一个二进制单元包和一个库单元包,它们用于与包相同的名字。我们可以在路径src/bin
下添加源文件来创建出更多的二进制单元包,这个路径下的每个源文件都会被视作单独的二进制单元包。
我们依赖的外部包,比如提供生成随机数功能的rand
包就属于单元包。将单元包的功能保留在它们自己的作用域中有助于指明某个特定功能来源于哪个单元包,并避免可能得命名冲突。
定义模块来控制作用域及私有性
通过下面的方式创建一个库单元包,RUST
也默认生成了单元测试的代码
cargo new --lib restaurant
// src/lib.rs mod front_of_house { mod host { fn add_to_waitlist() {} fn seat_at_table() {} } mod serving { fn take_order() {} fn serve_order() {} fn take_payment() {} } }
通过mod
关键字开头来定义一个模块,接着指明这个模块的名称,并在其后使用一对花括号来包裹模块体。模块内可以定义其他模块,同样也可以包含其它条目的定义,比如结构体、枚举、常量等。
我们前面提到过,src/main.rs
与src/lib.rs
被称为单元包的根节点,因为这两个文件的内容各自组成了一个名为crate
的模块,并位于单元包模块结构的根部。这个模块结构也被称为模块树(module tree
),整个模块树都被放置在一个名为crate
的隐式根模块下:
crate └── front_of_house ├── hosting │ ├── add_to_waitlist │ └── seat_at_table └── serving ├── take_order ├── serve_order └── take_payment
为了在RUST
模块树中找到某个条目,我们需要指定条目的路径,有两种形式:
- 使用单元包或字面量
crate
从根节点开始的绝对路径 - 使用
slef
、super
或内部标识符从当前模块开始的相对路径
绝对路径与相对路径都至少由一个标识符组成,标识符之间使用双冒号(::
)分隔。
// src/lib.rs pub fn eat_at_restaurant() { // 绝对路径 crate::front_of_house::host::add_to_waitlist(); // 相对路径 front_of_house::host::add_to_waitlist(); }
我们使用绝对路径和相对路径来调用add_to_waitlist
函数,大部分开发者更倾向使用绝对路径,因为我们往往会彼此独立地移动代码的定义与代码调用。
这段代码编译器报错,因为模块host
是私有的。模块不仅仅被用于组织代码,同时还定义了RUST
的私有边界(privacy boundary
):外部代码无法访问那些由私有边界封装的细节。
RUST
中的所有条目(函数、方法、结构体、枚举、模块及常量)默认都是私有的。处于父模块中的条目无法使用子模块中的私有条目,但子模块中的条目可以使用祖先模块中的条目。虽然子模块包装并隐藏了自身的实现细节,但它却依然能够感知当前定义环境的上下文。
我们需要给hosting
模块添加pub
关键字,之后我们便拥有了访问hosting
子模块的权利。然后,我们再给add_to_waitlist
添加pub
关键字,私有性问题就解决了。整个过程中,编译正常通过而front_of_house
模块并没有声明为pub
,是因为front_of_house
和eat_at_restaurant
被定义在相同的模块下。
fn server_oreder() {} mod back_of_house { fn fix_incorrent_order() { cook_order(); super::server_oreder(); } fn cook_order() {} }
代码从父模块开始构建相对路径,这一方式需要在路径起始处使用super
关键字。这有些类似于在文件系统中使用..
语法开始一段路径。例子中,我们通过super
关键字来跳转至back_of_house
的父模块,也就是根模块。
结构体及枚举声明为公开
当我们在结构体定义前使用pub
时,结构体本身就成为了公共结构体,但它的字段依旧保持了私有状态。我们可以逐一决定是否将某个字段公开。
枚举与结构体不同,由于枚举只有在所有变体都公开时才能实现最大的功效,而为所有枚举变体添加pub
则显得繁琐,因此所有的枚举变体默认都是公开的。但前提是我们将枚举声明为公开。
用use
将路径导入作用域
基于路径调用函数的写法使用起来有些重复和冗长,我们可以借助use
关键字将路径引入作用域,并像使用本地条目一样来调用路径中的条目。
mod front_of_house { pub mod host { pub fn add_to_waitlist() {} } } use crate::front_of_house::host; pub fn eat_at_restaurant() { host::add_to_waitlist(); }
通过在单元包的根节点下添加use crate::front_of_house::host
,host
成为该作用域下的一个有效名字,就如同host
模块被定义在根节点下一样。当然,使用use
将路径引入作用域时也需要遵守私有性规则。
实例中使用了绝对路径,使用相对路径也是可以的:use front_of_house::host
。
使用as
提供新的名称
使用use
将同名类型引入作用域时,可以在路径后使用as
关键字为类型指定一个新的本地名字,也就是别名。
use std::fmt::Result; use std::io::Result as IoResult;
使用嵌套的路径来清理众多use
语句
use std::io; use std::io::Write;
这两条拥有共同的前缀std::io
,该前缀还是第一条路径本身。可以在嵌套路径中使用self
将两条路径合并至一行use
语句中。
use std::io::{self, Write};
到此这篇关于rust 包模块组织结构的文章就介绍到这了,更多相关rust 包模块内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!