Home
Perfecto的头像

Perfecto

Rust 语法学习 - 模块系统

Rust 的模块系统包含几个相关概念,共同管理代码的组织、作用域和私有性:

  • 包 (Packages): Cargo 的功能,允许构建、测试和共享 crate
  • Crates: 一个树形模块结构,形成库或可执行程序
  • 模块 (Modules)与 use: 控制路径的组织、作用域和私有性
  • 路径 (Paths): 为函数、结构体等项命名的方式

5.1 包和 Crate

Crate

Crate 是 Rust 编译的最小代码单位,有两种形式:

  • 二进制 crate: 编译成可执行程序,必须有 main 函数
  • 库 crate: 不编译为可执行程序,提供给其他 crate 使用的功能

crate 根是源代码文件,Rust 编译器从这里开始构建 crate 的根模块。

包(package)是提供一系列功能的一个或多个 crate 的集合。包由 Cargo.toml 文件定义,描述如何构建这些 crate。

包的规则:

  • 包最多包含一个库 crate
  • 可以包含任意数量的二进制 crate
  • 至少包含一个 crate(库或二进制)
my_package/
├── Cargo.toml
├── src/
│   ├── main.rs       // 二进制crate根,名称与包名相同
│   ├── lib.rs        // 库crate根,名称与包名相同
│   └── bin/          // 额外的二进制crate
│       ├── bin1.rs   // 二进制crate: my_package_bin1
│       └── bin2.rs   // 二进制crate: my_package_bin2

5.2 定义模块控制作用域和私有性

模块基础

模块是在 crate 内组织代码的方式,提供可读性和重用性,并控制项(函数、类型、常量等)的私有性。

默认情况下,模块中的项对父模块私有,但父模块中的项不能使用子模块中的私有项。

// 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() {}
    }
}

模块树结构:

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

公开性与访问控制

使用 pub 关键字使项公开:

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();
}

super 关键字

super 关键字用于在相对路径中引用父模块的项:

fn serve_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order(); // 调用父模块的函数
    }

    fn cook_order() {}
}

结构体和枚举的公开性

  • 结构体公开后,其字段仍然默认私有
  • 枚举公开后,其所有变体自动公开
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 enum Appetizer {
        Soup,     // 公开变体
        Salad,    // 公开变体
    }
}

pub fn eat_at_restaurant() {
    let mut meal = back_of_house::Breakfast::summer("Rye");
    meal.toast = String::from("Wheat");
    // meal.seasonal_fruit = String::from("blueberries"); // 错误! 私有字段

    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

5.3 use 关键字

use 关键字将路径引入作用域,减少长路径重复:

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

use crate::front_of_house::hosting;
// 也可以使用相对路径: use front_of_house::hosting;

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

使用 use 的惯用语法

  • 函数: 一般引入到函数的父模块
  • 结构体/枚举/其他: 一般引入完整路径
// 函数推荐写法
use crate::front_of_house::hosting;
hosting::add_to_waitlist();

// 结构体/枚举推荐写法
use std::collections::HashMap;
let mut map = HashMap::new();

重命名引入的类型

使用 as 重命名引入项,解决名称冲突:

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

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

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

重新导出名称

结合 pubuse 重新导出项,外部代码可以用新路径访问:

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();
}

外部代码可以使用 our_crate::hosting::add_to_waitlist()

使用外部包

Cargo.toml 中添加依赖:

[dependencies]
rand = "0.8.5"

然后使用 use 导入:

use rand::Rng;

fn main() {
    let secret_number = rand::thread_rng().gen_range(1..=100);
}

嵌套路径

简化多个来自同一路径的项的导入:

// 原始写法
use std::cmp::Ordering;
use std::io;

// 嵌套路径写法
use std::{cmp::Ordering, io};

// 自身与子路径
use std::io::{self, Write};

通配符

导入路径下的所有公有项:

use std::collections::*;

5.4 将模块拆分为不同文件

随着模块变大,可以将其移动到单独的文件:

// src/lib.rs
mod front_of_house;  // 声明模块,内容在front_of_house.rs

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
// src/front_of_house.rs
pub mod hosting;  // 声明模块,内容在hosting.rs
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}

另一种方式是使用目录:

src/
├── lib.rs
├── front_of_house.rs
└── front_of_house/
    └── hosting.rs

5.5 工作区 (Workspaces)

工作区是由多个相关的包组成的集合,共享同一个 Cargo.lock 文件和输出目录。

# Cargo.toml
[workspace]
members = [
    "adder",
    "add_one",
]
my_workspace/
├── Cargo.toml
├── Cargo.lock
├── adder/
│   ├── Cargo.toml
│   └── src/
│       └── main.rs
├── add_one/
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs
└── target/

工作区中的依赖关系

在工作区内的包可以相互依赖:

# adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
// adder/src/main.rs
use add_one;

fn main() {
    let num = 10;
    println!("{} + 1 = {}", num, add_one::add_one(num));
}

测试工作区

在工作区根目录运行测试会测试所有包:

cargo test -p add_one  # 测试特定包
cargo test             # 测试所有包
Rust语法学习