Skip to content

Rust - 模块

随着项目规模的增长,代码组织变得至关重要。Rust 提供了一个强大的系统,基于包(crates)、模块(modules)和工作区(workspaces)。

术语描述
包(Crate)Rust 中最小的编译单元。包可以是二进制包(一个带有 main 函数的可执行程序)或库包(可重用代码的集合)。
包(Package)零个或多个包的捆绑,提供一组功能。包包含一个 Cargo.toml 文件,描述如何构建这些包。一个包最多可以包含一个库包,但可以包含许多二进制包。
模块(Module)一种将包内的代码组织成逻辑单元并控制项目可见性(公共/私有)的方式。
工作区(Workspace)一组共享公共 Cargo.lock 文件和输出目录的包。非常适合管理具有多个相互连接组件的大型项目。

模块使用 mod 关键字声明。默认情况下,模块内的所有内容都是私有的。使用 pub 关键字可以使项(模块、函数、结构体等)变为公共的,并可从模块外部访问。

mod network {
// 此函数对 `network` 模块私有。
fn connect() {
println!("Private connect function called");
}
// 此模块是公共的。
pub mod server {
// 此函数是公共的,因此可以从外部调用。
pub fn listen() {
println!("Listening on server...");
// 我们可以从同一个父模块调用私有函数。
super::connect();
}
}
}
fn main() {
// 这之所以有效,是因为 `server` 和 `listen` 都是公共的。
network::server::listen();
// 这将导致编译失败,因为 `connect` 是私有的:
// network::connect();
}

输入完整的路径(如 network::server::listen)可能会很繁琐。use 关键字可以为路径创建快捷方式,使代码更简洁。

// ... (上面定义的 network 模块)
// 将 `listen` 函数引入当前作用域
use network::server::listen;
fn main() {
// 现在我们可以直接调用它
listen();
}

习惯上使用 use 将结构体、枚举和其他项完全引入作用域。对于函数,通常更清晰的做法是引入父模块并使用其模块前缀调用函数(例如 server::listen()),以明确函数来源。

项目:在工作区中创建和使用库包

Section titled “项目:在工作区中创建和使用库包”

让我们创建一个更真实的工程结构:一个包含应用程序逻辑库和一个使用该库的二进制的工作区。

mkdir media_app
cd media_app
touch Cargo.toml

编辑 media_app/ 根目录下的新 Cargo.toml 文件,将其定义为工作区:

[workspace]
members = [
"media_lib",
"media_cli",
]

在 media_app 目录内部,使用 Cargo 创建库包:

cargo new media_lib --lib

编辑 media_app/media_lib/src/lib.rs 以包含我们的公共 API:

/// 一个用于播放媒体的公共函数。
pub fn play(name: &str) {
println!("Playing media: {}", name);
}
/// 一个表示音频轨道的公共结构体。
pub struct Audio {
pub title: String,
pub artist: String,
}

现在,创建将使用我们库的命令行应用程序:

cargo new media_cli

编辑 media_app/media_cli/Cargo.toml 来告知它关于 media_lib:

[package]
name = "media_cli"
version = "0.1.0"
edition = "2021"
[dependencies]
media_lib = { path = "../media_lib" }

最后,编辑 media_app/media_cli/src/main.rs 以调用我们库的代码:

// 使用我们本地库包中的项
use media_lib::{play, Audio};
fn main() {
println!("--- Media CLI Application ---");
// 调用库函数
play("Interstellar Soundtrack.mp4");
// 使用库结构体
let track = Audio {
title: "Time".to_string(),
artist: "Hans Zimmer".to_string(),
};
println!("Now loading audio: '{}' by {}", track.title, track.artist);
}

从工作区根目录(media_app/)中,你可以专门运行二进制包:

cargo run -p media_cli
Compiling media_lib v0.1.0 (...)
Compiling media_cli v0.1.0 (...)
Finished dev [unoptimized + debuginfo] target(s) in ...
Running `target/debug/media_cli`
--- Media CLI Application ---
Playing media: Interstellar Soundtrack.mp4
Now loading audio: 'Time' by Hans Zimmer