定义控制作用域和隐私的模组

Defining Modules to Control Scope and Privacy

在本节中,我们将讨论模组和模组系统的其他部分,即允许咱们给项目取名的 路径,paths、将路径引入作用域的 use 关键字,以及将项目构造为公开的 pub 关键字等。我们还将讨论 as 关键字、外部包和 glob 操作符。

首先,我们将以一份方便咱们今后组织咱们代码的规则清单开始。然后,我们将详细解释每一条规则。

模组速查表

Modules Cheat Sheet

这里我们提供了有关模组、路径、use 关键字和 pub 关键字,在编译器中如何运作,以及大多数开发人员如何组织代码的快速参考。我们将在本章节中,逐个讨论这些规则的示例,但这是个很到的,作为模组工作原理的提醒的地方。

  • 从代码箱根开始:编译代码箱时,编译器首先会在代码箱根文件(通常是库代码箱的 src/lib.rs,或二进制代码箱的 src/main.rs)中,查找要编译的代码;
  • 声明模组:在代码箱根文件中,咱们可以声明出新的模组;比如,咱们以 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 中。
  • 模组中代码的路径:一旦某个模组成为咱们代码箱的一部分,只要隐私规则允许,咱们就可以在同一代码箱的任何其他地方,使用代码的路径来引用该模组中的代码。例如,花园蔬菜模组中的 Asparagus (芦笋)类型,就可以在 crate::garden::vegetables::Asparagus 处找到。

  • 私有与公开:模组内的代码默认对其父模组是私有的。要将某个模组构造为公开,可使用 pub mod 代替 mod 声明该模组。要将某个公开模组中的项目,也构造为公开,请在其声明前使用 pub

  • use 关键字:在作用域中,use 关键字会创建出到项目的快捷方式,以减少长路径的重复。在任何可以引用 crate::garden::vegetables::Asparagus 的作用域中,咱们都可以使用 use crate::garden::vegetables::Asparagus; 创建出一个快捷方式,并在那以后,咱们就只需写下 Asparagus,就可以在该作用域中使用这个类型。

在此,我们创建了一个说明这些规则的名为 backyard 的二进制代码箱。这个代码箱的目录也称为 backyard,其中包含下面这些文件和目录:

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

本例中的代码箱根文件是 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

#![allow(unused)]
fn main() {
pub mod vegetables;
}

在这里,pub mod vegetables; 表示也包含了 src/garden/vegetables.rs 中的代码。这些代码是:

#![allow(unused)]
fn main() {
#[derive(Debug)]
pub struct Asparagus {}
}

现在就来进入到这些规则的细节,并在实际操作中对他们进行演示吧!

把有关联的代码分组在模组中

Grouping Related Code in Modules

模组,modules 让我们可以把代码组织在代码箱中,以提高可读性并方便重复使用。模组还允许我们控制程序项目的私密性,因为某个模组内的代码默认为私有。私有项目是一些内部的实现细节,不供外部使用。我们可以选择将模组及其中的项目公开,这样就可以让外部代码使用和依赖他们。

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

在餐饮业,餐厅的一些部分被称为 前厅,front of house,其他部分被称为 后厨,back of house。前厅是顾客所在的地方;包括餐厅领台为顾客安排座位、服务员接受点单和付款,以及调酒师调制饮料的地方。后厨是厨师和厨工在厨房工作、洗碗工进行清洁和经理进行行政工作的地方。

为了以这种方式架构我们的代码箱,我们可以将其函数,组织成一些嵌套的模组。请运行 cargo new restaurant --lib,创建一个名为 restaurant 的新库;然后在 src/lib.rs 中,输入下面清单 7-1 中的代码,定义一些模组和函数签名。下面是前台部分:

文件名:src/lib.rs

#![allow(unused)]
fn main() {
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 模组

我们使用后跟模组名字(本例中为 front_of_house)的 mod 关键字,定义某个模组。随后模组的主体,位于花括号内。在模组内部,我们可以放置其他模组,就像本例中 hostingserving 模组。模组还可以包含其他项目的定义,如结构体、枚举、常量、特质,以及如同清单 7-1 中的函数。

经由模组的使用,就可以将有关联的一些定义,组织在一起,并以他们因何相关而取个名字。使用此代码的程序员们,就可以根据这些分组,而非通读全部的这些定义,来浏览代码,那么在找到他们想要使用的那些定义时,就会容易一些。而对于要往该代码增加新功能的那些程序员,就清楚在哪里放置代码,来保持程序组织有序。

前面我们曾提到,src/main.rssrc/lib.rs 被称为代码箱根。之所以叫这两个文件为代码箱根,是因为这两个文件的内容,构成了位于该代码箱结构(即 模组树,module tree)根部,名为 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 中代码的模组树

该树显示了其中一些模组是如何嵌套在另一模组中的;例如,hosting 嵌套在 front_of_house 中。该树还显示其中一些模组彼此属于 同辈,siblings,即他们被定义在同一模组中;hostingservingfront_of_house 中定义的同辈份模组。如果模组 A 包含在模组 B 中,我们就说模组 A 是模组 B 的子模组,而模组 B 是模组 A 的父模组。请注意,整个模组树的根,都位于名为 crate 的隐式模组下。

模组树可能会让咱们联想到电脑上的文件系统目录树;这是个非常恰当的比较!就像文件系统中的目录一样,咱们可以使用模组来组织代码。而就像目录中的文件一样,我们需要一种找到咱们模组的方法。

Last change: 2024-01-01, commit: 9d790a1