第七章:模块系统与包管理——组织代码的艺术
本章导读:当你的代码从几百行增长到几万行,如何组织代码就变得至关重要。Rust 提供了一套清晰的模块系统,帮助你管理代码的可见性、组织文件结构。配合 Cargo 包管理器,你可以轻松地复用代码和分享库。本章将带你深入理解 Package、Crate、Module 之间的关系,以及如何使用 Cargo 管理依赖。
📦 7.1 代码组织的层次结构
Rust 的代码组织有几个层次,理解它们的关系是掌握模块系统的基础。
🏗️ 7.1.1 Package 和 Crate
Package 是一个 Cargo 项目,包含一个 Cargo.toml 文件。一个 Package 可以包含多个 Crate,但必须至少包含一个(库或二进制)。
Crate 是 Rust 编译的基本单位。有两种类型:
- 二进制 Crate:可以编译成可执行程序,必须有
main函数 - 库 Crate:定义可被其他 Crate 使用的功能,没有
main函数
my_project/
├── Cargo.toml # Package 配置
├── src/
│ ├── main.rs # 二进制 Crate(与 Package 同名)
│ ├── lib.rs # 库 Crate(与 Package 同名)
│ └── bin/
│ └── tool.rs # 额外的二进制 Crate
命名约定:如果 Package 名为
myproject</code>,那么 <code>src/main.rs</code> 编译成的二进制文件也叫 <code>myproject,src/lib.rs形成的库也以my_project为名。
🧩 7.1.2 Module
Module 是 Crate 内部的组织单元,用于分组代码和控制可见性。
// src/lib.rs
mod network {
pub fn connect() {
println!("连接网络");
}
mod server {
pub fn start() {
super::connect();
println!("服务器启动");
}
}
}
pub use network::server::start; // 重导出
🗂️ 7.2 Module 的定义和使用
📝 7.2.1 内联 Module
最简单的方式是在文件中直接定义:
// src/lib.rs
mod english {
pub fn hello() {
println!("Hello!");
}
fn goodbye() { // 私有函数
println!("Goodbye!");
}
}
mod chinese {
pub fn hello() {
println!("你好!");
}
}
pub fn greet(lang: &str) {
match lang {
"en" => english::hello(),
"zh" => chinese::hello(),
_ => println!("???"),
}
}
📁 7.2.2 文件 Module
当代码变多时,可以将 Module 放在单独的文件中:
src/
├── lib.rs
└── network.rs # network module
// src/lib.rs
mod network; // 声明 module,告诉编译器查找 network.rs
pub fn connect() {
network::connect();
}
// src/network.rs
pub fn connect() {
println!("连接网络");
}
📂 7.2.3 目录 Module
对于更复杂的 Module,可以使用目录结构:
src/
├── lib.rs
└── network/
├── mod.rs # network module 入口
├── server.rs # network::server
└── client.rs # network::client
// src/lib.rs
mod network;
// src/network/mod.rs
pub mod server;
pub mod client;
pub fn connect() {
println!("连接网络");
}
// src/network/server.rs
pub fn start() {
println!("服务器启动");
}
现代风格:Rust 2018 以后,可以省略
mod.rs,直接用network.rs作为目录入口:src/ ├── lib.rs ├── network.rs # network module 入口 └── network/ ├── server.rs └── client.rs
🔐 7.3 可见性控制
Rust 的默认可见性是私有的,需要用 pub 关键字公开。
🚪 7.3.1 基本可见性
mod outer {
pub fn public_function() {
println!("公开函数");
private_function();
}
fn private_function() {
println!("私有函数");
}
pub mod inner {
pub fn inner_public() {
println!("内部公开函数");
}
fn inner_private() {
println!("内部私有函数");
}
}
}
fn main() {
outer::public_function(); // ✓
// outer::private_function(); // ✗ 私有
outer::inner::inner_public(); // ✓
// outer::inner::inner_private(); // ✗ 私有
}
🎯 7.3.2 细粒度可见性
mod outer {
pub fn public() {}
// 只对当前 crate 公开
pub(crate) fn crate_visible() {}
// 只对父 module 公开
pub(super) fn parent_visible() {}
// 只对特定 module 公开
pub(in crate::outer::inner) fn specific_visible() {}
pub mod inner {
// 对所有祖先公开
pub(super) fn call_parent() {
super::parent_visible();
super::crate_visible(); // 同一 crate 内可见
}
}
}
📤 7.3.3 结构体和枚举的可见性
mod types {
pub struct Person {
pub name: String, // 公开字段
age: u32, // 私有字段
}
impl Person {
pub fn new(name: String, age: u32) -> Self {
Self { name, age }
}
pub fn age(&self) -> u32 {
self.age
}
}
// 枚举的变体继承枚举的可见性
pub enum Status {
Active, // 因为 Status 是 pub,所以 Active 也是 pub
Inactive,
}
}
fn main() {
let person = types::Person::new(String::from("张三"), 30);
println!("姓名: {}", person.name);
// println!("年龄: {}", person.age); // 错误!私有字段
println!("年龄: {}", person.age()); // 通过方法访问
}
📥 7.4 use 关键字
use 将路径引入当前作用域,简化代码。
🎯 7.4.1 基本用法
use std::collections::HashMap;
use std::io::Read;
use std::io::Write;
// 合并相同前缀
use std::io::{Read, Write};
// 引入所有公开项(慎用)
use std::collections::*;
// 引入 module 本身
use std::collections;
🔄 7.4.2 重命名和重导出
use std::io::Result as IoResult; // 重命名
pub use std::collections::HashMap; // 重导出(公开别人的模块)
🌳 7.4.3 相对路径
mod network {
pub fn connect() {}
pub mod server {
pub fn start() {
super::connect(); // 父级 module
crate::network::connect(); // 从 crate 根开始
self::local_fn(); // 当前 module
fn local_fn() {}
}
}
}
⚠️ 7.4.4 避免的习惯
// ❌ 不推荐:污染命名空间
use std::collections::*;
// ❌ 不推荐:两个同名类型
use std::collections::HashMap;
use std::collections::hash_map::HashMap; // 冲突!
// ✓ 推荐:使用完整路径或 as
use std::collections::HashMap;
use std::collections::hash_map::HashMap as StdHashMap;
// ✓ 推荐:函数调用使用完整路径
fn main() {
std::collections::HashMap::new();
}
📚 7.5 Cargo 包管理
Cargo 是 Rust 的官方包管理器和构建系统。
📋 7.5.1 Cargo.toml 配置
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
description = "一个很棒的项目"
license = "MIT"
repository = "https://github.com/user/my_project"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"
[dev-dependencies]
criterion = "0.5" # 仅在测试和 benchmark 中使用
[build-dependencies]
cc = "1.0" # 仅在 build.rs 中使用
[features]
default = ["feature1"]
feature1 = []
feature2 = ["dep:some-crate"]
[profile.release]
opt-level = 3
lto = true
📦 7.5.2 添加和管理依赖
# 添加依赖
cargo add serde
cargo add serde --features derive
cargo add tokio --features full
# 添加开发依赖
cargo add --dev criterion
# 移除依赖
cargo rm serde
# 更新依赖
cargo update
cargo update -p serde # 只更新 serde
# 查看依赖树
cargo tree
🔨 7.5.3 常用命令
# 创建项目
cargo new my_project
cargo new --lib my_library
# 构建
cargo build # debug 模式
cargo build --release # release 模式
# 检查(更快,不生成二进制文件)
cargo check
# 运行
cargo run
cargo run -- arg1 arg2 # 传递参数
# 测试
cargo test
cargo test test_name # 运行特定测试
# 文档
cargo doc
cargo doc --open
# 格式化
cargo fmt
# Lint
cargo clippy
# 发布
cargo publish
🗂️ 7.5.4 Workspace
对于包含多个相关 crate 的项目:
my_workspace/
├── Cargo.toml # workspace 配置
├── crate_a/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
├── crate_b/
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs
└── target/ # 共享的编译输出目录
# 根目录 Cargo.toml
[workspace]
members = [
"crate_a",
"crate_b",
]
🧪 7.6 实战:构建一个模块化的命令行工具
让我们创建一个模块化的 CLI 工具,展示文件组织和可见性控制:
filetool/
├── Cargo.toml
└── src/
├── main.rs
├── lib.rs
├── commands/
│ ├── mod.rs
│ ├── copy.rs
│ └── list.rs
└── utils/
├── mod.rs
└── format.rs
# Cargo.toml
[package]
name = "filetool"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "4.0", features = ["derive"] }
// src/lib.rs
pub mod commands;
pub mod utils;
pub use commands::{copy_files, list_files};
// src/commands/mod.rs
mod copy;
mod list;
pub use copy::copy_files;
pub use list::list_files;
// 内部共享函数
pub(super) fn validate_path(path: &str) -> std::io::Result<()> {
std::fs::metadata(path)?;
Ok(())
}
// src/commands/copy.rs
use super::validate_path;
pub fn copy_files(source: &str, dest: &str) -> std::io::Result<()> {
validate_path(source)?;
validate_path(dest)?;
let content = std::fs::read(source)?;
std::fs::write(dest, content)?;
println!("已复制 {} -> {}", source, dest);
Ok(())
}
// src/commands/list.rs
use super::validate_path;
pub fn list_files(path: &str) -> std::io::Result<()> {
validate_path(path)?;
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let name = entry.file_name().to_string_lossy().to_string();
let is_dir = entry.file_type()?.is_dir();
println!("{} {}", if is_dir { "📁" } else { "📄" }, name);
}
Ok(())
}
// src/utils/mod.rs
pub mod format;
pub fn print_separator() {
println!("{}", "=".repeat(40));
}
// src/utils/format.rs
pub fn format_size(bytes: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if bytes >= GB {
format!("{:.2} GB", bytes as f64 / GB as f64)
} else if bytes >= MB {
format!("{:.2} MB", bytes as f64 / MB as f64)
} else if bytes >= KB {
format!("{:.2} KB", bytes as f64 / KB as f64)
} else {
format!("{} B", bytes)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_size() {
assert_eq!(format_size(500), "500 B");
assert_eq!(format_size(1024), "1.00 KB");
assert_eq!(format_size(1048576), "1.00 MB");
}
}
// src/main.rs
use clap::Parser;
use filetool::{copy_files, list_files, utils};
#[derive(Parser)]
#[command(name = "filetool")]
#[command(about = "一个简单的文件工具", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(clap::Subcommand)]
enum Commands {
/// 列出目录内容
List {
/// 目录路径
path: String,
},
/// 复制文件
Copy {
/// 源文件
source: String,
/// 目标文件
dest: String,
},
}
fn main() {
let cli = Cli::parse();
utils::print_separator();
match cli.command {
Commands::List { path } => {
if let Err(e) = list_files(&path) {
eprintln!("错误: {}", e);
}
}
Commands::Copy { source, dest } => {
if let Err(e) = copy_files(&source, &dest) {
eprintln!("错误: {}", e);
}
}
}
utils::print_separator();
}
📝 本章小结
本章我们学习了 Rust 的模块系统和 Cargo 包管理:
- Package 是 Cargo 项目,包含
Cargo.toml - Crate 是编译的基本单位,分为二进制和库
- Module 组织代码和控制可见性
- <code>pub</code> 控制可见性,
pub(crate)、pub(super)提供细粒度控制 - <code>use</code> 引入路径,
pub use重导出 - Cargo 管理依赖、构建、测试
关键要点:
- 默认私有,需要时才公开
- 使用目录结构组织大型 module
- 避免过度使用
use * - 使用 workspace 管理多 crate 项目
在下一章,我们将学习迭代器和闭包——Rust 函数式编程的精髓。
动手实验:
- 创建一个 workspace,包含一个库 crate 和一个二进制 crate,让二进制 crate 使用库 crate 的功能。
- 为本章的 filetool 添加一个新命令
info,显示文件的大小和修改时间。- 使用
pub(crate)限制某些函数只在 crate 内可见,尝试从外部访问它们,观察编译错误。