Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Rust 项目结构、模块系统与包管理


1️⃣ Rust 项目结构

Rust 项目通常由 Cargo 管理,Cargo 是 Rust 的官方包管理和构建工具。

更多cargo命令见Cargo命令参考,本节不在介绍cargo 如何创建项目等操作。

项目根目录文件

文件 / 目录作用结构概览
Cargo.toml项目配置文件,管理 crate 信息、依赖、版本、特性等TOML 格式,包含 [package][dependencies][dev-dependencies][workspace]
Cargo.lock依赖锁定文件,记录精确依赖版本,保证跨环境一致性自动生成,列出每个 crate 的精确版本和来源
toolchain.toml可选手动添加,指定 Rust toolchain 版本和 channelTOML 格式,如 [toolchain] channel = "stable"
target/构建输出目录编译生成的二进制文件、依赖 crate 编译产物、doc 文档等
.gitignoreGit 忽略文件配置通常忽略 target/Cargo.lock(库 crate 可选)
README.md项目说明文档Markdown 格式
LICENSE许可证文件如 MIT、Apache 2.0 等
rustfmt.toml可选,格式化规则配置 rustfmt 行为
clippy.toml可选,lint 规则配置 clippy 检查规则

src/ 目录

文件 / 目录作用结构概览
main.rs二进制 crate 入口fn main() { ... }
lib.rs库 crate 入口公共接口,通常声明模块和导出函数
mod.rs模块目录入口(可选)声明子模块 pub mod xxx;
子模块文件实现模块功能xxx.rsxxx/mod.rs,内部函数和类型

示例结构:

src/
├── main.rs
├── lib.rs
├── utils.rs
└── math/
    ├── mod.rs
    └── add.rs

tests/ 目录

  • 集成测试
  • 每个文件作为一个测试模块,Cargo 会自动识别并执行
tests/
└── integration_test.rs

示例内容:integration_test.rs

#![allow(unused)]
fn main() {
// 引入库 crate
use my_project::utils;
use my_project::math::add;

#[test]
fn test_add_function() {
    let result = add::add(2, 3);
    assert_eq!(result, 5);
}

#[test]
fn test_utils_uppercase() {
    let input = "hello";
    let output = utils::to_uppercase(input);
    assert_eq!(output, "HELLO");
}
}

examples/ 目录(可选)

  • 示例代码
  • 用于演示库 crate 用法,cargo run --example example_name 执行
examples/
└── hello.rs

benches/ 目录(可选)

  • 性能测试
  • cargo bench 执行
  • 常用 Criterion 库写基准测试

target/ 目录

  • 编译输出目录

  • 包含:

    • debug/release/ 构建产物
    • 编译依赖的 crate(cargo build 缓存)
    • doc/ 自动生成文档

说明: 不需要手动管理,由 Cargo 生成。


Cargo 自动生成与手动添加文件总结

文件类型是否自动生成作用
Cargo.toml配置自动项目信息、依赖、features
Cargo.lock锁定自动精确依赖版本
toolchain.toml配置手动/可选指定 Rust toolchain 版本
target/输出自动编译产物、文档、缓存
main.rs / lib.rs代码自动(cargo new)入口文件 / 库接口
mod.rs / 子模块代码手动模块组织
tests/ / examples/ / benches/代码手动可选测试、示例、基准
README.md / LICENSE文档手动可选项目说明 / 许可证
rustfmt.toml / clippy.toml配置手动可选格式化 / Lint

2️⃣ Rust 模块系统

模块系统管理 作用域和可见性,允许将代码组织成清晰层次。


2.1 mod 声明

  • 声明模块
  • 可以在同文件或单独文件定义

示例:单文件模块

mod utils {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
}

fn main() {
    let sum = utils::add(2, 3);
    println!("{}", sum);
}

示例:多文件模块

项目结构:

src/
├── main.rs
└── utils.rs

main.rs

mod utils;   // 引入 utils.rs 文件

fn main() {
    let sum = utils::add(2, 3);
    println!("{}", sum);
}

utils.rs

#![allow(unused)]
fn main() {
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
}

2.2 子模块

  • 使用 mod xxx; 声明子模块
  • 可以在 mod.rs 或目录下 xxx.rs 文件定义

示例项目结构:

src/
├── main.rs
└── math/
    ├── mod.rs
    └── add.rs

math/mod.rs

#![allow(unused)]
fn main() {
pub mod add;    // 引入 add.rs
}

math/add.rs

#![allow(unused)]
fn main() {
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}
}

main.rs

mod math;

fn main() {
    let sum = math::add::add(2, 3);
    println!("{}", sum);
}

2.3 可见性控制

Rust 的默认模块 是私有的,意味着模块内部的函数、结构体、常量等默认 只能在当前模块内访问

要让其他模块或 crate 能访问,就需要使用 pub 及其变体 来控制可见性。

可见性关键字

可见性作用访问范围
pub公开整个 crate 或外部 crate 可见
pub(crate)crate 内部可见同一个 crate 内都可以访问,外部 crate 不可见
pub(super)父模块可见仅对父模块和父模块的兄弟模块可见
pub(in path)指定模块可见只能在指定模块或其子模块访问

示例解析

假设项目结构如下:

src/
├── main.rs
└── a/
    ├── mod.rs
    └── sub.rs

示例代码

#![allow(unused)]
fn main() {
mod a {
    fn private_fn() {
        println!("只能在 a 模块内部访问");
    }

    pub fn public_fn() {
        println!("对外公开,可被 main.rs 调用");
    }

    pub(crate) fn crate_fn() {
        println!("在当前 crate 内可见,但外部 crate 不可访问");
    }

    pub(super) fn parent_fn() {
        println!("对父模块可见(main.rs 或兄弟模块)");
    }

    pub(in crate::a) fn in_module_fn() {
        println!("只在 a 模块或子模块可见");
    }
}
}

main.rs 调用规则

fn main() {
    a::public_fn();      // ✅ 可访问
    a::crate_fn();       // ✅ 可访问(同 crate)
    a::parent_fn();      // ✅ 可访问(main.rs 是父模块)
    // a::private_fn();  // ❌ 编译报错,私有
    // a::in_module_fn(); // ❌ 编译报错,不在指定模块范围
}

可见性理解图(文字版)

my_crate
│
├─ main.rs           <- 父模块
└─ a/ mod.rs         <- 子模块 a
      ├─ private_fn()        // 只在 a 内部
      ├─ public_fn()         // crate 内 + 外部 crate
      ├─ crate_fn()          // crate 内
      ├─ parent_fn()         // 父模块可见(main.rs)
      └─ in_module_fn()      // 仅在 a 模块或子模块

mod 默认行为

#![allow(unused)]
fn main() {
mod a { ... }
}

前面需不需要加 pub,这取决于 你希望这个模块是否对外可见:


mod 默认行为
  • mod a { ... }

    • 默认是私有的
    • 只在 父模块 内可见
    • 外部模块或 crate 不能直接访问

pub mod 的作用
  • 写成 pub mod a { ... }

    • 模块变成 公共模块
    • 父模块外部可以访问
    • 在库 crate 中,外部 crate 可以用 my_crate::a::foo() 调用

示例:库 crate

#![allow(unused)]
fn main() {
// lib.rs
pub mod a {
    pub fn public_fn() {
        println!("外部 crate 可访问");
    }

    fn private_fn() {
        println!("仅 a 模块内部可访问");
    }
}
}

在外部 crate 调用:

#![allow(unused)]
fn main() {
// main.rs in other crate
my_crate::a::public_fn(); // ✅ 可访问
// my_crate::a::private_fn(); // ❌ 编译报错
}

模块层级总结
声明模块可见性访问范围
mod a私有父模块内可访问
pub mod a公共父模块 + 父模块外可访问
子模块内部函数默认私有私有仅在该模块内部可访问
子模块内部函数 pub公共父模块可访问,如果父模块 pub mod,外部 crate 也可访问

小结
  • 库 crate:如果希望外部 crate 使用模块,pub mod a 必须加 pub
  • 二进制 crate / 内部模块:通常不用 pub,默认私有即可
  • 模块内部的函数、类型:再用 pub 或可见性修饰符控制访问范围

2.4 导入、使用函数

分为两种情况: 有lib.rs 无lib.rs

没有 lib.rs(纯二进制 crate)

假设项目:

src/
├── main.rs            //main.rs 是根模块,并声明了 math 模块和 utils 模块 mod utils; mod math;
├── utils.rs
└── math/
    ├── mod.rs
    └── add.rs

编译器查找顺序:

mod utils; → 查找 src/utils.rs 或 src/utils/mod.rs (两者按照道理不会同时存在) mod math; → 查找 src/math.rs 或 src/math/mod.rs (两者按照道理不会同时存在)

总结:根模块(main.rs)是入口 → 模块声明 → 模块路径解析 → use 按模块层级查找。

有 lib.rs(库 crate)

假设项目:

src/
├── main.rs
├── lib.rs          //lib.rs 声明模块 pub mod utils;  pub mod math;
├── utils.rs
└── math/
    ├── mod.rs
    └── add.rs

编译器查找顺序:

crate 根是 lib.rs 按 pub mod 声明顺序查找模块文件 utils → src/utils.rs 或者 src/utils/mod.rs math → src/math.rs 或者 src/math/mod.rs

注意:外部 crate 调用你的库总是从 lib.rs 根开始(用 crate::my_crate:: 路径)

如果项目有 lib.rs,main.rs 想使用 math/add.rs 中的函数,有哪些导入方式

以有 lib.rs(库 crate)为例,导入 math/add.rs 中的函数,有哪些导入方式

1.通过 lib.rs 中重新导出函数(lib.rs中声明了 pub mod math; )

use my_project::add;   // 直接使用重新导出的函数

fn main() {
    let result = add(5, 6);
    println!("5 + 6 = {}", result);
}

2.通过 mod math 直接导入

mod math;
use math::add::add;  

fn main() {
    let result = add(2, 3);
    println!("2 + 3 = {}", result);
}

Rust 模块文件查找原则

1、默认情况下,Rust 只会在 crate 根(src/)及其子目录 查找模块。 2、mod xxx; 会去以下路径查找: xxx.rs xxx/mod.rs 3、如果模块不在 src/ 内部,编译器不会自动找到,除非通过 路径引入或 workspaces。

my_project/
├── Cargo.toml
├── src/
│   └── main.rs
└── math/
    └── add.rs

这里 math/ 不在 src/ 内,所以:

直接写 mod math; 会报错,编译器找不到 src/math.rssrc/math/mod.rs 需要告诉 Rust 显式路径 或 把 math 变成 crate(workspace)的一部分)

把 math 作为子 crate,通过 path 依赖进行声明( 或者构建workspace,math 下新创建 Cargo.toml)

2.5 Rust 导入路径:crate:: vs my_crate::

1、 crate:: - 表示当前 crate 根 - 只在同一个 crate 内部使用 - 可以从根模块(lib.rs 或 main.rs)开始相对引用模块和函数 - 常用于库内部模块之间调用 - crate:: 只能在 同一个 crate 内使用,外部 crate 不可用。 2、my_crate:: - 表示外部 crate 的名称 - 外部 crate 调用你的库时使用 - crate 名就是你在 Cargo.toml中[package] name = “my_crate” 中定义的名称 - 外部 crate 必须通过 crate 名来访问,你不能用 crate::,因为 crate:: 指的是调用者自己的根模块。

3️⃣ Crate 与包管理

3.1 crate 类型

  • 二进制 crate:生成可执行程序,入口 main.rs
  • 库 crate:提供可复用代码,入口 lib.rs
  • 一个 Cargo 项目可以同时包含二进制 crate 和库 crate。

3.2 依赖管理

依赖类型

[dependencies]
regex = "1"                  # 精确版本
serde = { version = "1.0", features = ["derive"] }  # 启用特性

[dev-dependencies]

[build-dependencies]

1、[dependencies]:声明项目运行依赖

[dependencies]
rand = "0.8"

Cargo 会下载 crate 并在构建时编译,默认导入到项目中可直接 use rand::Rng;

2、[dev-dependencies]:开发依赖(测试、示例代码)

[dev-dependencies]
criterion = "0.4"

只在测试或基准测试中使用,cargo build 不会编译 dev-dependencies,cargo test 或 cargo bench 会编译

3、[build-dependencies]:构建脚本依赖

[build-dependencies]
cc = "1.0"

仅用于构建脚本 build.rs,编译时 Cargo 会使用这些依赖生成文件或处理构建任务

4、可选依赖 / features

4.1 可选依赖(optional dependency) - 一个依赖 不一定总被编译 - 只有在需要时才启用,节省编译时间和二进制大小 - 常用于库 crate,允许用户选择功能模块

[dependencies]
serde = { version = "1.0", optional = true ,features = ["derive"]}  # optional = true 表示可选 ,依赖不默认编译

4.2 Features - 用来 组合可选依赖或控制功能 - 可以在 Cargo.toml 中声明,默认启用或手动启用 - 控制是否启用 optional 依赖或功能

[features]
default = []                  # 默认启用的依赖有[]中的内容,此处为空,默认没有任何 feature 启用
json_support = ["serde_json"] # json_support feature 启用 serde/json

4.3 main.rs 或库代码中使用可选依赖

#[cfg(feature = "json_support")]   // 仅在 feature 启用时编译
pub fn parse_json(s: &str) -> serde_json::Value {
    serde_json::from_str(s).unwrap()
}

#[cfg(feature = "xxx")] 宏控制 编译条件,如果用户没启用 json_support,这段代码不会编译.

4.4 如何启用 Features

[dependencies]
serde_json = { version = "1.0", optional = true }

[features]
default = []
json = ["serde_json"]

可选依赖只有在指定 feature 时才会编译,通过 –features json 激活,cargo build --features json_support。默认构建 cargo build (不启用任何 feature) 启用多个 feature(假设有多个)cargo build --features "json_support other_feature"


版本管理规则

Cargo 遵循 语义化版本(SemVer):

MAJOR.MINOR.PATCH

1、^ 号(Caret):默认依赖策略

serde = "^1.0" # >=1.0.0, <2.0.0

2、~ 号(Tilde):仅允许小版本更新

serde = "~1.0.115" # >=1.0.115, <1.1.0

3、= 号(Equal):指定精确版本:

serde = "=1.0.115" # 仅 1.0.115

4、* 号(Star):指定通配符:

serde = "1.*" # >=1.0.0, <2.0.0

3.3 Rust Workspace(工作区)结构

Workspace 概念 - Workspace 是 多个 crate 的组合 - 共享同一个 Cargo.lock - 可以统一构建、测试和管理依赖 - 常用于大型项目或多个相关 crate 的组织

Workspace 根目录 - 根目录 必须有 Cargo.toml,声明 workspace 成员 - 根目录下通常不放源码,源码在各个子 crate 中

示例:

my_workspace/
├── Cargo.toml         # workspace 根配置
├── crate_a/           # 第一个 crate
│   ├── Cargo.toml
│   └── src/
│       └── lib.rs
├── crate_b/           # 第二个 crate
│   ├── Cargo.toml
│   └── src/
│       └── main.rs
└── target/            # 构建输出,workspace 共享

根 Cargo.toml 配置:

[workspace]
members = [
    "crate_a",
    "crate_b",
]

[workspace.dependencies]
//内部依赖
crate_a = { path = "../crate_a" }
crate_b = { path = "../crate_b" }
//外部依赖
serde = { version = "1.0" ,features = ["derive"]}

members 列出 workspace 内的所有 crate, 可以是相对路径,也可以是子目录

1 子 crate 相互依赖

假设 crate_b 依赖 crate_a:

[dependencies]
crate_a = { workspace = true }  # 路径依赖

main.rs 中调用:

use crate_a::some_function;

fn main() {
    some_function();
}

2 Workspace 优点 - 统一管理依赖,避免重复下载 - 多 crate 构建和测试统一 - 子 crate 相互依赖方便(用 path) - 适合大型项目和库 + 可执行程序组合