Rust 的“家族隐私”法则:深入理解 pub 与可见性
作为一名大学生,你可能已经写过一些 Rust 代码,用过 pub 关键字。但你是否曾困惑:为什么有些函数在模块外无法调用?为什么有些结构体字段无法直接访问?这背后其实是 Rust 精心设计的一套可见性规则,它就像一套“家族隐私法则”,用于管理代码的访问权限。
一、从一个简单的“家族”类比开始
想象一下,你的大学里有一个社团(Crate)。这个社团里有很多部门(Module),比如“技术部”、“宣传部”等。
Crate (社团):是你项目的最外层,一个独立的编译单元。可以是一个二进制程序(
src/main.rs)或一个库(src/lib.rs)。Module (部门):用于组织代码、控制可见性(隐私)的命名空间。
在这个社团里,每个部门(模块)都有自己的“家规”,规定了哪些家当(函数、结构体、变量等)是:
完全公开的:任何人都可以来看、来用。
部门内部使用的:只有本部门的成员才能使用。
对友邻部门开放的:只有社团内部的其他部门可以使用,外人不行。
Rust 的可见性系统,就是在帮我们定义这些“家规”。
二、核心关键字:pub 的不同修饰符
pub 关键字就是用来声明“这个东西是公开的”。但公开给谁?这里有不同的级别:
| 可见性级别 | 关键字 | 类比解释 |
|---|---|---|
| 私有 | (无,默认) | 部门内部的秘密文件,只有本部门的人能看。 |
| 完全公开 | pub | 社团的海报,贴在校内公告栏,校内校外的人都能看。 |
| 在父模块中可见 | pub(super) | 给上级领导(父部门)看的汇报材料。 |
| 在整个Crate内可见 | pub(crate) | 社团内部共享的文档,所有部门都能看,但不会发给校外的人。 |
| 在指定路径可见 | pub(in path::to::module) | 指定只给某个“友邻部门”看的资料。 |
默认情况(私有):在 Rust 中,所有项(函数、结构体、枚举、常量等)默认都是私有的。这体现了 Rust 的安全哲学:除非你明确允许,否则一切都是封闭的。
三、图示:一个代码“社团”的结构
让我们来看一个具体的代码例子和它的结构图。
// src/lib.rs - 我们的“社团”总部pub mod department_a { // 部门Apub fn public_function() { // 完全公开的函数println!("Anyone can call me!");}pub(crate) fn internal_docs() { // 社团内部使用的函数println!("For crate eyes only.");}fn private_function() { // 部门私有的函数println!("Top secret of department A!");}pub mod secret_team { // 部门A下的一个秘密小组use super::private_function; // 可以访问上级的私有项pub fn do_secret_work() {println!("Secret team is working...");private_function(); // 合法!小组可以调用部门的私有函数}}
}pub mod department_b { // 部门Buse crate::department_a;pub fn try_access() {department_a::public_function(); // 合法!完全公开department_a::internal_docs(); // 合法!同属一个crate// department_a::private_function(); // 错误!无法访问其他部门的私有项// department_a::secret_team::do_secret_work(); // 合法!因为do_secret_work是pub的}
}// 来自“校外”的访问(比如在 main.rs 中)
use my_crate::department_a;fn main() {department_a::public_function(); // 合法!// department_a::internal_docs(); // 错误!校外的人看不到内部文档
}现在,我们用一张图来直观展示上面的访问关系:
text+--------------------------------------------------------------------+
| Crate: my_crate (社团) |
| |
| +---------------------------+ +---------------------------+ |
| | Module: department_a | | Module: department_b | |
| | | | | |
| | +---------------------+ | | +---------------------+ | |
| | | pub fn public_fn |<------|--| (可以访问) | | |
| | | (对外完全公开) | | | | | | |
| | +---------------------+ | | +---------------------+ | |
| | | | | |
| | +---------------------+ | | +---------------------+ | |
| | | pub(crate) internal |<-------|--| (可以访问) | | |
| | | (仅社团内部可见) | | | | | | |
| | +---------------------+ | | +---------------------+ | |
| | | | | |
| | +---------------------+ | | +---------------------+ | |
| | | fn private_fn | | | | (无法访问!) | | |
| | | (部门A私有) | | | | | | |
| | +---------------------+ | | +---------------------+ | |
| | ^ | | | |
| | | (super可以访问) | +---------------------------+ |
| | +---------------------+ | |
| | | mod secret_team | | |
| | | | | +---------------------------+ |
| | | +-----------------+ | | | External: src/main.rs | |
| | | | (可以调用 | | | | (校外) | |
| | | | private_fn) | | | | | |
| | | +-----------------+ | | | +---------------------+ | |
| | +---------------------+ | | | 只能访问 public_fn |--|------+
| +---------------------------+ | | (无法访问internal!) | | |
| | +---------------------+ | |
+--------------------------------------------------------------------+图解说明:
实线箭头:表示允许的访问。
虚线箭头并带“叉”:表示被规则禁止的访问。
department_b可以访问department_a的public和pub(crate)项,因为它们同属一个 Crate。secret_team可以访问其父模块department_a的私有项,这是super路径的威力。外部的
main.rs只能访问被标记为pub的项,pub(crate)的项对它来说是隐藏的。
四、结构体与枚举的可见性
可见性规则同样适用于复合类型,但有一点小变化。
1. 结构体(Struct)
结构体本身的可见性和其字段的可见性是分开的。
pub mod department_a {// 这个结构体对外部是公开的pub struct OpenHouse {pub address: String, // 地址也是公开的pub(crate) internal_phone: String, // 内部电话只有crate内能访问secret_code: u32, // 密码是绝对私有的,外界甚至无法访问这个字段}impl OpenHouse {// 一个公共的构造函数,是创建OpenHouse的唯一方式pub fn new(addr: String, phone: String, code: u32) -> Self {OpenHouse {address: addr,internal_phone: phone,secret_code: code,}}// 一个公共的方法,可以间接读取私有字段pub fn check_code(&self, code: u32) -> bool {self.secret_code == code}}
}// 在外部使用
use my_crate::department_a::OpenHouse;fn main() {let house = OpenHouse::new("Rust Ave".to_string(), "123".to_string(), 42);println!("Address: {}", house.address); // 合法// println!("Phone: {}", house.internal_phone); // 错误!外部无法访问// println!("Code: {}", house.secret_code); // 错误!字段私有house.check_code(50); // 合法,通过公共接口与私有字段交互
}要点:即使结构体是 pub 的,其字段也默认是私有的。这实现了封装,你可以精确控制哪些数据可以直接修改,哪些必须通过方法。
2. 枚举(Enum)
枚举的可见性规则更简单:如果枚举是 pub 的,那么它的所有变体也都是公开的。
pub mod department_a {pub enum PublicEvent { // 公开的枚举OpenSeminar, // 所有变体自动公开Workshop, // 所有变体自动公开InternalMeeting, // 所有变体自动公开}
}这是因为枚举的变体是其类型的组成部分,将它们全部公开通常更有用。
五、为什么要这样设计?给大学生的启示
契约与安全:可见性规则在你(代码作者)和用户(其他程序员,包括未来的你)之间建立了一份清晰的“契约”。它明确指出了哪些功能是稳定的、可供外部使用的 API,哪些是可能变化的内部实现细节。这避免了用户误用内部接口,从而在你修改内部代码时,他们的代码不会崩溃。
高内聚,低耦合:鼓励你将相关的数据和行为封装在模块内。模块内部可以自由地修改和重构,只要不改变其公开的接口,就不会影响到外部代码。这是构建大型、可维护软件系统的基石。
清晰的抽象边界:通过强制使用
pub来暴露接口,Rust 促使你思考:“这个函数/结构体真的需要被外界使用吗?” 这有助于设计出更清晰、更抽象的 API。
总结
可以把 Rust 的可见性系统想象成一个精密的权限管理系统:
默认私有:保护你的代码,防止意外耦合。
pub:打开大门,完全公开。pub(crate):一个非常实用的中间地带,用于创建crate 内部的 API,对外隐藏实现细节。pub(super)和pub(in path):用于更精细的作用域控制,在复杂的模块树中非常有用。
