FORMA

第七章:模块系统与包管理

Rust 的模块系统为代码组织、封装和复用提供了一套严谨但灵活的机制。包、crate、模块以及 use 路径相互配合,让你能够把项目拆解成清晰的功能单元,同时精确控制哪些对外可见。本章将带你系统掌握这些构建大型 Rust 项目所必需的组织工具。

7.1 包(Package)与 Crate

在 Rust 中,代码的基本编译单元是 crate,而发布和版本管理的单元是 package

Crate

一个 crate 是一个独立的编译单元。编译器一次处理一个 crate,生成库或可执行文件。每个 crate 都有一个 crate root,即编译的入口源文件:

  • 库 crate 的根为 src/lib.rs
  • 二进制 crate 的根为 src/main.rs(也可以有其它二进制入口,例如 src/bin/foo.rs,每个对应一个二进制 crate)

在代码中,crate 关键字代表当前 crate 的根模块,用于构建绝对路径:

rust
crate::some_module::some_function();

Package

一个 package(包)由一个 Cargo.toml 文件描述,它可以包含:

  • 0 或 1 个库 crate
  • 任意数量的二进制 crate

典型的新项目 cargo new my_app 生成的是一个包含单一二进制 crate 的包(src/main.rs)。如果同时需要库和二进制,可以在 src/lib.rssrc/main.rs 中分别编写,二进制部分通过 crate 名引用库:

rust
// src/main.rs
use my_app::some_function;

很多项目将核心逻辑放在库 crate 中,二进制 crate 只保留轻量的入口点,这样便于测试和复用。

7.2 模块(mod)

模块用来在一个 crate 内部将代码组织成命名空间,控制条目的私有性。你可以把模块理解为代码的目录结构。

定义模块

模块可以用内联方式定义:

rust
mod sound {
    pub fn play() {
        println!("Playing sound");
    }
}

或者用文件系统映射。在 main.rs / lib.rs 中写下 mod my_module; 时,编译器会去寻找:

  • my_module.rs(在同一目录下)
  • my_module/mod.rs(旧风格,但仍可用)

2018 edition 后,通常推荐直接创建一个与模块同名的 .rs 文件,避免 mod.rs 造成的文件堆积。例如:

text
src/
├── main.rs
└── sound.rs

main.rs 中声明 mod sound;sound.rs 内即为 sound 模块的内容。模块也可以嵌套,只需相应地创建子目录和文件。

模块可见性

所有模块、函数、结构体等条目默认为私有,只对当前模块及其子模块可见。使用 pub 关键字将其公开:

rust
mod plant {
    pub struct Vegetable { .. }
    fn private_function() { .. }
}
fn main() {
    let v = plant::Vegetable { .. }; // 允许,Vegetable 为 pub
    // plant::private_function();    // 错误,私有
}

路径:绝对路径与相对路径

访问模块内条目使用 :: 分隔符。路径可以是:

  • 绝对路径:从 crate 根开始,使用 crate:: 或字面 crate 名(外部 crate 用其名)
  • 相对路径:从当前模块开始,使用 self::super:: 或直接标识符
rust
crate::front_of_house::hosting::add_to_waitlist(); // 绝对路径
front_of_house::hosting::add_to_waitlist();        // 相对路径

super 指向上级模块,类似于文件系统中的 ..

rust
super::some_function();

self 表示当前模块,通常用于消除歧义或明确引入。

use 导入

use 语句将路径引入当前作用域,减少重复书写长路径:

rust
use std::collections::HashMap;
let mut map = HashMap::new();

在模块内部使用 use 时,路径默认是绝对路径(从 crate 根开始),除非用 selfsuper 指定相对。习惯上,函数通过保留父路径来区分来源:

rust
use crate::front_of_house::hosting;
hosting::add_to_waitlist(); // 清楚显示函数来自 hosting

对于结构体、枚举等,则通常直接引入完整名称:

rust
use std::collections::HashMap;

pub use 重导出

pub use 可以让一个条目在当前模块公开的同时,从另一个路径导入,从而对外暴露一个不同的 API 结构。这在构建友好的公共接口时非常有用:你可以把内部细节组织得很深,但通过 pub use 将核心类型重新放置到顶层模块中。

rust
// lib.rs
mod engine;
pub use engine::Core; // Core 现在可以直接通过 crate::Core 访问

模块拆分建议

  • 功能相关:将紧密协作的类型和函数放在同一模块。
  • 扁平优先:不要过深嵌套,通常两到三层模块已足够。过深的层级会增加 use 噪音。
  • 合理暴露:谨慎使用 pub,隐藏实现细节有助于未来修改。

7.3 可见性细节

除了简单的 pub / 私有之外,Rust 还支持细粒度的可见性修饰,精确控制条目对哪些范围开放。

pub(crate) — crate 内公开

pub(crate) 使条目在当前 crate 内部公开,但对下游的 crate 使用者不可见。它常用于内部模块借用,而不想暴露为公共 API。

rust
pub(crate) fn internal_helper() { .. }

pub(super) — 父模块内公开

pub(super) 让条目对父模块(上一级)可见,但对外部模块隐藏。这有助于在父模块内部分享一些子模块的细节。

pub(in path) — 指定路径内公开

可以传入一个具体路径,表示条目只对该路径内的代码可见。例如 pub(in crate::some_module) 使条目仅对 some_module 及其后代可见。

结构体字段的可见性

结构体本身可以是 pub,但它的字段可以各自拥有不同的可见性,从而构建出精细封装的 API:

rust
pub struct User {
    pub username: String,      // 完全公开
    pub(crate) token: String,  // crate 内可见
    password_hash: String,     // 私有
}

这样,外部代码只能访问 username,而 token 只能在 crate 内部使用,password_hash 则完全隐藏。

7.4 外部依赖与 use

Cargo.toml 中添加依赖后,便可以在代码中通过 crate 名引用其内容。

用 crate 名引入

外部 crate 的根模块用其名称访问,这与本地模块的绝对路径类似:

rust
use serde::Deserialize;

当外部 crate 名称含有连字符时,Rust 会自动将其转换为下划线,例如 my-crate 在代码中写为 my_crate

重命名

使用 as 为导入的条目起别名,解决命名冲突或提升可读性:

rust
use std::io::Result as IoResult;
fn read() -> IoResult<()> { .. }

引入多个条目

利用花括号合并同一前缀的导入:

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

等价于:

rust
use std::cmp::Ordering;
use std::io;

还可以嵌套,例如 use std::io::{self, Read}; 同时导入 io 模块本身和 Read trait。

glob 导入

使用 * 一次性引入某个模块中所有公开条目:

rust
use std::collections::*;

虽然方便,但容易污染命名空间,降低可读性,且可能引入意想不到的名字冲突。通常只推荐在测试模块或预导入模块(prelude)中使用。

管理外部依赖的结构

合理组织 use 语句是保持代码整洁的关键。常见的风格是将 use 分为若干组,并用空行分隔:标准库导入、外部 crate 导入、本地模块导入(crate::)。还可以按深度排序,使得文件头部一目了然。

通过包与 crate 界定编译单元,通过模块组织命名空间,再配合精确的可见性与 use 导入,Rust 的模块系统在保障封装性的同时提供了极高的灵活性。掌握了这些规则,你就能轻松驾驭从单文件脚本到大型工作空间的任何项目结构。

参考文献

资料说明
Packages and crates官方书第 7 章
Cargo workspaces工作空间
Cargo 手册包管理

相关文章