rust- 定义模块以控制作用域和隐私

发布于:2025-07-28 ⋅ 阅读:(19) ⋅ 点赞:(0)

定义模块以控制作用域和隐私

本节中,我们将讨论模块及模块系统的其他部分,即路径(允许你为项命名);use 关键字(将路径引入作用域);以及 pub 关键字(使项公开)。我们还会讨论 as 关键字、外部包和通配符操作符。

模块速查表

在详细介绍模块和路径之前,这里提供一个关于编译器中模块、路径、use 和 pub 关键字如何工作的快速参考,以及大多数开发者如何组织代码。本章将通过示例逐条讲解这些规则,但这里是回顾模块工作原理的好地方。

  • 从 crate 根开始:
    编译 crate 时,编译器首先查看 crate 根文件(通常库 crate 是 src/lib.rs,二进制 crate 是 src/main.rs)中的代码进行编译。

  • 声明模块:
    在 crate 根文件中,你可以声明新模块;例如声明一个 “garden” 模块使用 mod garden; 。编译器会在以下位置寻找该模块的代码:

    • 内联,在用花括号替代 mod garden 后分号的位置内写代码块
    • 文件 src/garden.rs 中
    • 文件 src/garden/mod.rs 中
  • 声明子模块:
    在除根文件之外的任何文件中,都可以声明子模块。例如,可以在 src/garden.rs 中声明 mod vegetables; 。编译器会在父级同名目录下寻找子模块代码,具体位置包括:

    • 内联,在用花括号替代 mod vegetables 后分号的位置内写代码块
    • 文件 src/garden/vegetables.rs 中
    • 文件 src/garden/vegetables/mod.rs 中
  • 访问模块中的代码路径:
    一旦某个模块成为你的 crate 的一部分,只要隐私规则允许,就可以从同一crate中的任意位置通过该代码的路径引用它。例如,garden::vegetables 模块里的 Asparagus 类型可通过 crate::garden::vegetables::Asparagus 来访问。

  • 私有与公共:
    默认情况下,一个模组内部的代码对其父模组是私有的。若想让一个模组变成公共,则需使用 pub mod 而非 mod 声明。同样,要让公共模组内的项也变成公有,需要在它们前面加上 pub 修饰符。

  • use 关键字:
    在某个作用域内,use 用于创建项目快捷方式,以减少长路径重复。在任何能引用到 crate::garden::vegetables::Asparagus 的作用域里,可以用 use crate::garden::vegetables::Asparagus; 创建快捷方式,此后只需写 Asparagus 即可使用该类型。

下面举例说明以上规则,我们创建了一个名为 backyard 的二进制crate,其目录结构如下:

backyard
├── Cargo.lock
├── Cargo.toml
└── src
    ├── garden
    │   └── vegetables.rs
    ├── garden.rs
    └── main.rs

此处crate根文件是src/main.rs,其内容如下:

Filename: src/main.rs

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() {
    let plant = Asparagus {};
    println!("I'm growing {plant:?}!");
}

pub mod garden; 告诉编译器包含位于src/garden.rs中的代码,该文件内容为:

Filename: src/garden.rs

pub mod vegetables;

这里 pub mod vegetables; 表示同时包含了src/garden/vegetables.rs中的内容,该文件内容为:

#[derive(Debug)]
pub struct Asparagus {}

接下来我们深入了解这些规则并演示其实践!

将相关代码归类到各个模组中

Modules 可以帮助我们组织crate内部的代码,提高可读性和复用性。同时,它们还能控制项目成员间隐私,因为默认情况下,一个module内部定义的是私有项,不对外暴露。这些“私有”元素仅作为实现细节存在,不供外部调用。如果需要,也可以选择把整个module或其中特定元素设为public,从而允许外部依赖调用它们。

举例来说,我们来写一个餐厅功能库crate,仅定义函数签名不实现具体逻辑,以便专注于如何组织这段程序,而不是餐厅业务本身。

餐饮行业常把餐厅划分成“前台”和“后台”。前台指客户所在区域,包括迎宾安排座位、服务员点单付款以及调酒师调饮料等环节;后台则是厨师做菜、洗碗工清洁及管理人员处理行政事务之地。

为了按此思路构建我们的crate,可采用嵌套modules来分类函数。在终端运行 cargo new restaurant --lib 新建库,然后编辑src/lib.rs,将7-1列表所示源码输入进去,实现前台(front_of_house)部分功能签名:

Filename: src/lib.rs

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}

        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}

        fn serve_order() {}

        fn take_payment() {}
    }
}

7-1列表:“front_of_house” module 包含两个子module,每个都含若干函数

我们用mod关键词加名称定义module(此处即front_of_house),然后把主体放入花括号{}内。在这个module里,还能继续嵌套其它modules,如hosting与serving。此外modules还能容纳structs, enums, constants, traits等各种定义,本例展示的是functions。

利用modules,我们能够聚合相关定义,并赋予其意义上的关联名称。这样程序员阅读时就能基于group定位目标,而不用遍历所有定义,更易找到关心部分;新增功能时也知道应往哪儿添加才能保持结构整齐。

此前提过,src/main.rs 与src/lib.r s被称作crateroots,是因为这两个源文件里的全部内容组成了顶层叫做crate名字空间(module)树状结构(root of the module tree)。

7-2列表显示了7-1列出的结构对应得module树形图:

crate 
└── front_of_house 
     ├── hosting 
     │   ├── add_to_waitlist 
     │   └── seat_at_table 
     └── serving 
         ├── take_order 
         ├── serve_order 
         └── take_payment     

7-2列表:“Listing 7–1”的Module树形图

这棵树展现了一些modules嵌套关系,比如hosting就是nest(嵌套) 在front_of_house里面。同时也体现出兄弟关系——hosting与serving都是front_of_house下平级兄弟节点。如果A module包含B module,则说B是A孩子节点(A parent)。注意整个tree由隐式root叫做crate起始。

这个module树很像电脑中文件系统目录树,非常贴切!正如filesystem里的目录用于整理存储,同理你借助modules整理你的code。而且跟directory里的files一样,你需要方法去定位自己的modules。

上面这段话太难理解,请你以图文形式展示。但是不要改变文章的意思。但你需要完整的返回文章内容


网站公告

今日签到

点亮在社区的每一天
去签到