在本章中,我们将学习 Rust 的包和模块系统,它们是组织和重用代码的重要工具。随着项目规模的增长,良好的代码组织变得越来越重要,Rust 提供了一套强大的机制来管理代码结构。
Crate 是 Rust 中最高级别的代码组织单位。一个 crate 可以是一个二进制项目或一个库。
main
函数main
函数包(package)是一个或多个 crate 的集合,提供一组功能。一个包包含一个 Cargo.toml
文件,描述如何构建这些 crate。
包的规则:
Cargo 是 Rust 的构建系统和包管理器,它处理许多任务:
# 创建二进制包
cargo new my_project
# 创建库包
cargo new my_library --lib
my_project/
├── Cargo.toml # 包配置文件
├── src/ # 源代码目录
│ └── main.rs # 二进制 crate 的根文件
└── target/ # 编译输出目录
对于库包,src/main.rs
被替换为 src/lib.rs
。
Cargo.toml
是包的配置文件,使用 TOML (Tom’s Obvious, Minimal Language) 格式:
[package]
name = "my_project"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"
模块是 Rust 中组织代码的方式,可以控制项(函数、结构体等)的私有性。
使用 mod
关键字创建模块:
// 在 lib.rs 或 main.rs 中
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
模块形成一个树状结构,根模块是 src/main.rs
或 src/lib.rs
:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
路径用于在模块树中找到项。路径可以是:
crate
self
、super
或当前模块的标识符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
关键字用于访问父模块中的项:
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order() {}
}
fn serve_order() {}
Rust 中的项默认是私有的。可以使用 pub
关键字使项公有:
pub fn
:公有函数pub struct
:公有结构体(字段仍然是私有的)pub enum
:公有枚举(所有变体都是公有的)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");
// 错误:seasonal_fruit 是私有的
// meal.seasonal_fruit = String::from("blueberries");
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
use
关键字可以将路径引入作用域,避免使用长路径:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
as
关键字可以为引入的项提供新名称:
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
使用 pub use
可以重新导出项,使其可以被外部代码访问:
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();
}
可以在一个 use
语句中引入多个项:
// 而不是:
// use std::io;
// use std::io::Write;
// 可以写成:
use std::io::{self, Write};
*
通配符可以引入路径下的所有公有项:
use std::collections::*;
这通常只在测试中使用,或者作为 prelude 模式的一部分。
随着模块增长,可能需要将它们移动到单独的文件中。
src/
├── front_of_house/
│ ├── hosting.rs
│ └── serving.rs
├── front_of_house.rs
└── lib.rs
// src/lib.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
// src/front_of_house.rs
pub mod hosting;
mod serving;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
fn seat_at_table() {}
// src/front_of_house/serving.rs
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
工作区是一组共享 Cargo.lock
和输出目录的包。
创建一个包含 Cargo.toml
的目录:
# Cargo.toml
[workspace]
members = [
"adder",
"add_one",
]
然后创建成员包:
cargo new adder
cargo new add_one --lib
add/
├── Cargo.lock
├── Cargo.toml
├── adder/
│ ├── Cargo.toml
│ └── src/
│ └── main.rs
└── add_one/
├── Cargo.toml
└── src/
└── lib.rs
工作区中的包可以相互依赖:
# adder/Cargo.toml
[dependencies]
add_one = { path = "../add_one" }
[package]
name = "my_crate"
version = "0.1.0"
edition = "2021"
description = "A description of my crate"
license = "MIT OR Apache-2.0"
cargo publish
Cargo 遵循语义化版本规范(SemVer):
让我们创建一个简单的库,展示 Rust 的模块系统:
// src/lib.rs
pub mod math;
pub mod utils;
pub fn greet() {
println!("Hello from my_library!");
utils::logging::log("Greeted the user");
}
// src/math.rs
pub mod arithmetic;
pub mod statistics;
pub fn is_even(n: i32) -> bool {
n % 2 == 0
}
// src/math/arithmetic.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn subtract(a: i32, b: i32) -> i32 {
a - b
}
pub fn multiply(a: i32, b: i32) -> i32 {
a * b
}
pub fn divide(a: i32, b: i32) -> Option<i32> {
if b == 0 {
None
} else {
Some(a / b)
}
}
// src/math/statistics.rs
pub fn mean(numbers: &[i32]) -> Option<f64> {
if numbers.is_empty() {
return None;
}
let sum: i32 = numbers.iter().sum();
Some(sum as f64 / numbers.len() as f64)
}
pub fn median(numbers: &[i32]) -> Option<f64> {
if numbers.is_empty() {
return None;
}
let mut sorted = numbers.to_vec();
sorted.sort();
let mid = sorted.len() / 2;
if sorted.len() % 2 == 0 {
Some((sorted[mid - 1] as f64 + sorted[mid] as f64) / 2.0)
} else {
Some(sorted[mid] as f64)
}
}
// src/utils.rs
pub mod logging;
// src/utils/logging.rs
pub fn log(message: &str) {
println!("[LOG]: {}", message);
}
pub fn error(message: &str) {
eprintln!("[ERROR]: {}", message);
}
使用这个库的二进制 crate:
// src/main.rs
use my_library::math::arithmetic;
use my_library::math::statistics;
use my_library::utils::logging;
fn main() {
my_library::greet();
let a = 10;
let b = 5;
println!("{}+{}={}", a, b, arithmetic::add(a, b));
println!("{}-{}={}", a, b, arithmetic::subtract(a, b));
println!("{}*{}={}", a, b, arithmetic::multiply(a, b));
match arithmetic::divide(a, b) {
Some(result) => println!("{}/{}={}", a, b, result),
None => logging::error("除以零错误"),
}
let numbers = vec![1, 5, 10, 15, 20];
println!("数字: {:?}", numbers);
if let Some(mean_value) = statistics::mean(&numbers) {
println!("平均值: {}", mean_value);
}
if let Some(median_value) = statistics::median(&numbers) {
println!("中位数: {}", median_value);
}
logging::log("程序执行完毕");
}
创建一个名为 geometry
的库 crate,包含计算不同形状(圆形、矩形、三角形)面积和周长的函数。使用适当的模块结构组织代码。
扩展上面的库,添加一个 utils
模块,包含用于验证输入值的函数(例如,检查边长是否为正数)。
创建一个二进制 crate,使用你的 geometry
库计算并打印不同形状的面积和周长。
创建一个工作区,包含两个相关的包:一个提供基本数学运算,另一个使用这些运算实现更复杂的功能。
修改 geometry
库,使其可以发布到 crates.io(添加适当的文档、描述和许可证信息)。
在本章中,我们学习了:
use
关键字简化路径良好的代码组织是构建可维护软件的关键。Rust 的模块系统提供了强大的工具,帮助你组织代码,控制可见性,并创建清晰的 API。随着你的 Rust 项目变得更加复杂,这些技能将变得越来越重要。