Skip to content

7. Package、Crate 和 Module

7.1 Package、Crate 和 Module 的定义

Rust 的代码组织包括:

  • 哪些细节可以暴露,哪些细节私有
  • 作用域内哪些名称有效

模块系统:

  • 包(Package):Cargo 的一个功能,可以让我们构建、测试、共享 crate
  • 单元包(Crate):一个模块树,可产生一个二进制文件或库
  • 模块(Module):控制代码组织、作用域和路径
  • 路径(Path):用于引用模块树中的项

Crate 的类型为 binary 和 library。src/main.rs 是二进制 crate 的入口,src/lib.rs 是库 crate 的入口。Crate Root 是编译器从 crate 开始的文件。

我们也可以在 src/bin 目录下创建多个二进制 crate。

一个 Package 包含一个 Cargo.toml 文件,描述如何构建 crate。只能包含一个库 crate,可以包含任意多个二进制 crate。但至少包含一个 crate。

Crate 可以将相关功能组合到一个作用域内,便于在项目间进行共享,而且能防止命名冲突。例如 rand crate 包含了随机数生成器,访问它的功能需要使用它的名字并 use rand

定义 Module 来控制作用域和私有性。在一个 crate 中,模块将代码进行分组。控制项目(Item)的可见性,可以使用 pub 关键字。

使用 mod 关键字创建模块。模块树形结构,模块可以包含在其他模块中。模块可以嵌套。

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

src/main.rssrc/lib.rs 文件是 crate root。这两个文件形成了名为 crate 的模块,位于模块树的根部。

7.2 路径

可以使用 crate 关键字来从根模块开始访问其他模块,也可以使用 super 关键字来访问父模块。也可以在模块的同一级别使用相对路径。

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

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::front_of_house::hosting::add_to_waitlist();

    // 相对路径
    front_of_house::hosting::add_to_waitlist();
}

模块不仅能组织代码,还能定义私有边界(privacy boundary)。如果想把函数或 struct 设为私有,可以方到模块内部。Rust 中所有条目默认是私有的,使用 pub 关键字来使其公有。

父模块无法访问子模块中的私有内容,但子模块可以访问父模块中的内容。

7.3 父级模块路径

使用 super 关键字来访问父级模块的内容。

rust
fn serve_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();
    }

    fn cook_order() {}
}

使用 pub struct 来定义公有结构体,但结构体的字段默认是私有的。可以使用 pub 关键字来使字段公有。

rust
mod back_of_house {
    pub struct Breakfast {
        pub toast: String,
        seasonal_fruit: String,
    }

    impl Breakfast {
        pub fn summer(toast: &str) -> Breakfast {
            Breakfast {
                toast: String::from(toast),
                seasonal_fruit: String::from("peaches"),
            }
        }
    }
}

枚举的成员默认是公有的,不需要在其前面加 pub 关键字。

7.4 使用 use 关键字

使用 use 关键字将路径引入作用域,可以使用相对路径或绝对路径。

一般对于 structenum 和函数,直接使用 use 关键字将其引入作用域,而不是引入其父级。

rust
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

同名模块必须指定到父级,或者使用 as 关键字重命名。

rust
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
}

fn function2() -> IoResult<()> {
    // --snip--
}

使用 use 引入的名称对于外部是私有的。如果想要将名称变为公有,可以使用 pub use

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

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

7.5 使用外部包

要使用外部包,可以在 Cargo.toml 文件中添加依赖项。Cargo 会在 crates.io 上查找依赖项。

下载好之后使用 use 关键字引入。

可以使用嵌套路径来引入外部包的子模块,语法为 use xxx::{yyy, zzz}

rust
use std::{cmp::Ordering, io};

如果要引入当前名称,使用 self 关键字。

rust
// use std::fmt::Result;
// use std::fmt;
use std::fmt::{self, Result};

我们可以使用 * 通配符来引入所有公有项。

rust
use std::collections::*;

7.6 模块的文件系统

如果定义模块时,直接使用分号而不是代码块,则 Rust 会从同名文件中加载模块。

rust
mod front_of_house;

模块层级与文件系统层级一致。例如 src/front_of_house.rssrc/front_of_house/hosting.rs