定义模块以控制范围和隐私


在本节中,我们将讨论模块和模块系统的其他部分,即路径,它允许你命名项目;将 path 引入范围的 use 关键字;和 pub 关键字将项目设为公共。我们还将讨论 as 关键字、外部包和 glob 运算符。


模块作弊表


在了解模块和路径的详细信息之前,我们在这里提供了有关模块、路径、use 关键字和 pub 关键字在编译器中如何工作,以及大多数开发人员如何组织代码的快速参考。在本章中,我们将介绍这些规则中的每一个示例,但这是一个很好的参考地方,可以提醒模块是如何工作的。


  • 从 crate 根开始:编译 crate 时,编译器首先查看 crate 根文件(通常为 src/lib.rs 用于库 crate 或 src/main.rs) 进行编译。

  • 声明模块:在 crate 根文件中,你可以声明新的模块;假设你用 Mod garden; 声明了一个 “garden” 模块。编译器将查找 对于这些位置的模块代码:

    • 内联,在大括号内,替换 mod garden 后面的分号

    • 在文件 src/garden.rs

    • 在文件 src/garden/mod.rs

  • 声明 submodules:在 crate 根以外的任何文件中,你可以声明 submodules。例如,你可以声明 mod vegetables;src/garden.rs 中。编译器将在 目录中:

    • 内联,紧跟在 mod vegetables 之后,在大括号而不是分号内

    • 在文件 src/garden/vegetables.rs

    • 在文件 src/garden/vegetables/mod.rs

  • 模块中的代码路径:一旦模块成为 crate 的一部分,您就可以 从同一 crate 中的其他任何位置引用该模块中的代码,只要 在隐私规则允许的情况下,使用 code 的路径。例如, 花园蔬菜模块中的芦笋类型可以在 crate::garden::vegetables::Asparagus .

  • 私有与公共:默认情况下,模块中的代码对其父模块是私有的。要将模块设为公共模块,请使用 pub mod 声明它 而不是 mod。要将公共模块中的项也设为公共,请使用 pub 的声明。

  • use 关键字:在范围内,use 关键字创建快捷方式 项以减少长路径的重复。在任何可以引用 crate::garden::vegetables::Asparagus ,你可以使用 Create a shortcut 创建快捷方式 use crate::garden::vegetables::Asparagus; ,从那时起,你只需要编写 Asparagus 即可在范围内使用该类型。


在这里,我们创建了一个名为 backyard 的二进制 crate 来说明这些规则。crate 的目录(也称为 backyard)包含这些文件和目录:

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


在本例中,crate 根文件是 src/main.rs,它包含:


文件名: 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,即:


文件名: src/garden.rs


Pub Mod 蔬菜;


这里,pub mod vegetables;意味着 src/garden/vegetables.rs 中的代码也被包含进来。该代码为:

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


现在让我们深入了解这些规则的细节并在实践中演示它们!


模块让我们在一个 crate 中组织代码,以实现可读性和易于重用。模块还允许我们控制项目的隐私,因为模块中的代码默认是私有的。私有项目是内部实现细节,不可用于外部使用。我们可以选择将模块和其中的项目设为公共,从而公开它们以允许外部代码使用和依赖它们。


例如,让我们编写一个提供餐厅功能的库 crate。我们将定义函数的签名,但将它们的主体留空,以便专注于代码的组织,而不是餐厅的实现。


在餐饮业中,餐厅的某些部分被称为 Front of House 和其他 Back of House 的。前台是客户所在的地方;这包括主人让顾客就座、服务员接受订单和付款以及调酒师制作饮料的地方。后勤是厨师和厨师在厨房工作、洗碗机清洁和经理进行行政工作的地方。


要以这种方式构建我们的 crate,我们可以将其函数组织到嵌套模块中。通过运行 cargo new restaurant --lib 创建一个名为 restaurant 的新库。然后将示例 7-1 中的代码输入到 src/lib.rs 中,定义一些模块和函数签名;此代码是 Front of house 部分。


文件名: 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 模块包含其他模块,然后包含函数


我们使用 mod 关键字后跟模块名称(在本例中为 front_of_house)定义一个模块。然后,模块的主体位于大括号内。在 modules 内部,我们可以放置其他 modules,就像在这种情况下 hostingserving 的模块 一样。模块还可以保存其他项目的定义,例如结构体、枚举、常量、trait 和函数,如示例 7-1 所示。


通过使用模块,我们可以将相关定义分组在一起,并命名它们相关的原因。使用此代码的程序员可以根据组导航代码,而不必通读所有定义,从而更容易找到与他们相关的定义。向此代码添加新功能的程序员将知道将代码放置在何处以保持程序井井有条。


前面我们提到了 src/main.rssrc/lib.rs 被称为 crate 根。之所以命名,是因为这两个文件的内容在板条箱的模块结构的根部形成了一个名为 crate 的模块,称为模块树


示例 7-2 显示了示例 7-1 中结构的模块树。

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


示例 7-2:示例 7-1 中代码的模块树


此树显示某些模块如何嵌套在其他模块中;例如 front_of_house 中托管巢穴。该树还显示某些模块是同级模块,这意味着它们在同一模块中定义;托管servingfront_of_house 中定义的同级。如果模块 A 包含在模块 B 中,则我们说模块 A 是模块 B 的模块,模块 B 是模块 A 的父模块。请注意,整个模块树都根在名为 crate 的隐式模块下。


模块树可能会让您想起计算机上文件系统的目录树;这是一个非常贴切的比较!就像文件系统中的目录一样,您可以使用模块来组织您的代码。就像目录中的文件一样,我们需要一种方法来查找我们的模块。