1142 lines
27 KiB
Markdown
1142 lines
27 KiB
Markdown
# 第四章 结构化工程:模块与包
|
||
|
||
## 4.1 模块系统概述
|
||
|
||
随着项目规模增长,代码组织变得至关重要。Rust的模块系统提供了强大的工具,帮助开发者构建清晰、可维护的代码结构。本章将探索如何使用模块、包和工作空间来组织大型Rust项目。
|
||
|
||
### 为什么需要模块化?
|
||
|
||
模块化设计带来的核心优势:
|
||
|
||
- **代码组织** - 逻辑相关的代码集中在一起
|
||
- **封装** - 隐藏实现细节,只暴露必要接口
|
||
- **命名空间** - 避免名称冲突
|
||
- **可重用性** - 促进代码复用
|
||
- **可测试性** - 更容易为独立模块编写测试
|
||
|
||
### Rust模块系统的核心概念
|
||
|
||
| 概念 | 描述 | 关键字/文件 |
|
||
|------|------|------------|
|
||
| 模块(Module) | 代码的命名空间单元 | `mod` |
|
||
| 包(Package) | 一个或多个crate的集合 | `Cargo.toml` |
|
||
| Crate | 编译单元,生成库或可执行文件 | `lib.rs`/`main.rs` |
|
||
| 工作空间(Workspace) | 多个相关包的集合 | `[workspace]` |
|
||
|
||
## 4.2 mod层级设计:构建代码树
|
||
|
||
### 模块声明与组织
|
||
|
||
Rust中的模块可以嵌套,形成树状结构:
|
||
|
||
```rust
|
||
// lib.rs
|
||
mod front_desk; // 前台模块
|
||
mod cataloging; // 编目模块
|
||
mod user_services; // 用户服务模块
|
||
|
||
// 嵌套模块示例
|
||
mod user_services {
|
||
mod registration {
|
||
// 用户注册相关功能
|
||
}
|
||
|
||
mod borrowing {
|
||
// 借阅相关功能
|
||
}
|
||
}
|
||
```
|
||
|
||
### 模块文件组织策略
|
||
|
||
Rust提供两种组织模块的方式:
|
||
|
||
1. **内联模块**:在同一文件中定义
|
||
```rust
|
||
// lib.rs
|
||
mod config {
|
||
pub const MAX_BOOKS: usize = 5;
|
||
|
||
pub fn get_loan_duration() -> u32 {
|
||
14 // 默认借阅期为14天
|
||
}
|
||
}
|
||
```
|
||
|
||
2. **文件模块**:在单独文件中定义
|
||
```
|
||
lib.rs // 声明模块
|
||
└── config.rs // 实现模块
|
||
```
|
||
|
||
```rust
|
||
// lib.rs
|
||
mod config; // 声明模块,实现在config.rs中
|
||
|
||
// config.rs
|
||
pub const MAX_BOOKS: usize = 5;
|
||
|
||
pub fn get_loan_duration() -> u32 {
|
||
14
|
||
}
|
||
```
|
||
|
||
### 子模块文件组织
|
||
|
||
对于更复杂的模块结构,可以使用目录:
|
||
|
||
```
|
||
lib.rs // 声明顶层模块
|
||
├── user_services.rs // 声明user_services子模块
|
||
└── user_services/ // 子模块目录
|
||
├── registration.rs
|
||
└── borrowing.rs
|
||
```
|
||
|
||
```rust
|
||
// user_services.rs
|
||
pub mod registration; // 声明子模块
|
||
pub mod borrowing; // 声明子模块
|
||
```
|
||
|
||
### 模块路径与引用
|
||
|
||
使用`use`关键字简化模块路径:
|
||
|
||
```rust
|
||
// 不使用use
|
||
fn main() {
|
||
crate::user_services::registration::register_new_user("Alice");
|
||
}
|
||
|
||
// 使用use
|
||
use crate::user_services::registration::register_new_user;
|
||
|
||
fn main() {
|
||
register_new_user("Alice");
|
||
}
|
||
```
|
||
|
||
**路径别名**:
|
||
|
||
```rust
|
||
use crate::user_services::registration::register_new_user as register;
|
||
|
||
fn main() {
|
||
register("Alice");
|
||
}
|
||
```
|
||
|
||
**批量导入**:
|
||
|
||
```rust
|
||
use crate::user_services::registration::{register_new_user, validate_email};
|
||
```
|
||
|
||
## 4.3 pub权限控制:接口与实现分离
|
||
|
||
### 可见性规则
|
||
|
||
Rust中的所有项默认是私有的,使用`pub`关键字使其公开:
|
||
|
||
```rust
|
||
mod library {
|
||
pub struct Book { // 公开结构体
|
||
pub title: String, // 公开字段
|
||
isbn: String, // 私有字段
|
||
}
|
||
|
||
impl Book {
|
||
pub fn new(title: &str, isbn: &str) -> Self { // 公开方法
|
||
Self {
|
||
title: title.to_string(),
|
||
isbn: isbn.to_string(),
|
||
}
|
||
}
|
||
|
||
fn validate_isbn(&self) -> bool { // 私有方法
|
||
!self.isbn.is_empty()
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 可见性级别
|
||
|
||
Rust提供多种可见性级别:
|
||
|
||
| 关键字 | 可见范围 | 用途 |
|
||
|--------|---------|------|
|
||
| (默认) | 仅当前模块及其子模块 | 隐藏实现细节 |
|
||
| `pub` | 任何引入该模块的代码 | 公开API |
|
||
| `pub(crate)` | 仅当前crate | 内部API |
|
||
| `pub(super)` | 仅父模块 | 限制子模块暴露 |
|
||
| `pub(in path)` | 指定路径 | 精细控制可见性 |
|
||
|
||
示例:
|
||
|
||
```rust
|
||
mod library {
|
||
pub(crate) struct Catalog { // 仅在crate内可见
|
||
// ...
|
||
}
|
||
|
||
pub(super) fn internal_helper() { // 仅对父模块可见
|
||
// ...
|
||
}
|
||
}
|
||
```
|
||
|
||
### 接口设计最佳实践
|
||
|
||
1. **最小公开原则**:只公开必要的API
|
||
2. **抽象泄漏控制**:使用私有字段防止实现细节泄漏
|
||
3. **API稳定性**:谨慎公开可能变化的内容
|
||
|
||
```rust
|
||
// 良好的接口设计
|
||
pub struct Library {
|
||
// 私有实现细节
|
||
books: Vec<Book>,
|
||
users: HashMap<UserId, User>,
|
||
}
|
||
|
||
impl Library {
|
||
// 公开稳定API
|
||
pub fn new() -> Self { /* ... */ }
|
||
pub fn add_book(&mut self, book: Book) { /* ... */ }
|
||
pub fn borrow_book(&mut self, user_id: UserId, book_id: BookId) -> Result<(), BorrowError> { /* ... */ }
|
||
|
||
// 私有辅助函数
|
||
fn find_available_copy(&self, book_id: BookId) -> Option<&Book> { /* ... */ }
|
||
}
|
||
```
|
||
|
||
## 4.4 workspace多crate管理:扩展到大型项目
|
||
|
||
### 工作空间基础
|
||
|
||
工作空间允许管理多个相关包:
|
||
|
||
```toml
|
||
# Cargo.toml (根目录)
|
||
[workspace]
|
||
members = [
|
||
"library-core",
|
||
"library-api",
|
||
"library-cli"
|
||
]
|
||
```
|
||
|
||
目录结构:
|
||
|
||
```
|
||
library-workspace/
|
||
├── Cargo.toml # 工作空间配置
|
||
├── library-core/ # 核心功能crate
|
||
├── library-api/ # API服务crate
|
||
└── library-cli/ # 命令行工具crate
|
||
```
|
||
|
||
### 工作空间依赖管理
|
||
|
||
工作空间内的crate可以相互依赖:
|
||
|
||
```toml
|
||
# library-api/Cargo.toml
|
||
[dependencies]
|
||
library-core = { path = "../library-core" }
|
||
```
|
||
|
||
### 工作空间命令
|
||
|
||
在工作空间根目录执行命令会应用到所有成员:
|
||
|
||
```bash
|
||
# 构建所有crate
|
||
cargo build
|
||
|
||
# 测试特定crate
|
||
cargo test -p library-core
|
||
|
||
# 运行特定crate
|
||
cargo run -p library-cli
|
||
```
|
||
|
||
### 共享依赖优化
|
||
|
||
工作空间中的共享依赖只编译一次,提高构建效率:
|
||
|
||
```toml
|
||
# 在各crate的Cargo.toml中
|
||
[dependencies]
|
||
serde = "1.0" # 在多个crate中使用的依赖只编译一次
|
||
```
|
||
|
||
## 4.5 文档测试:代码即文档
|
||
|
||
### 文档注释类型
|
||
|
||
Rust支持两种文档注释:
|
||
|
||
1. **外部文档注释**:用于项的文档
|
||
```rust
|
||
/// 表示图书馆中的一本书
|
||
///
|
||
/// # 示例
|
||
/// ```
|
||
/// let book = Book::new("Rust编程", "978-1234567890");
|
||
/// assert_eq!(book.title(), "Rust编程");
|
||
/// ```
|
||
pub struct Book {
|
||
// ...
|
||
}
|
||
```
|
||
|
||
2. **内部文档注释**:用于模块或crate整体文档
|
||
```rust
|
||
//! # 图书馆管理系统
|
||
//!
|
||
//! 这个模块提供图书馆管理的核心功能,包括:
|
||
//! - 图书编目
|
||
//! - 用户管理
|
||
//! - 借阅流程
|
||
```
|
||
|
||
### 文档测试
|
||
|
||
文档中的代码块默认会作为测试运行:
|
||
|
||
```rust
|
||
/// 计算借阅的逾期费用
|
||
///
|
||
/// # 参数
|
||
/// * `days_overdue` - 逾期天数
|
||
///
|
||
/// # 返回值
|
||
/// 应付的逾期费用(元)
|
||
///
|
||
/// # 示例
|
||
/// ```
|
||
/// use library::calculate_overdue_fee;
|
||
/// assert_eq!(calculate_overdue_fee(5), 5.0); // 5天逾期,收费5元
|
||
/// assert_eq!(calculate_overdue_fee(0), 0.0); // 未逾期,无费用
|
||
/// ```
|
||
pub fn calculate_overdue_fee(days_overdue: u32) -> f64 {
|
||
days_overdue as f64
|
||
}
|
||
```
|
||
|
||
运行文档测试:
|
||
|
||
```bash
|
||
cargo test --doc
|
||
```
|
||
|
||
### 文档测试特殊标记
|
||
|
||
控制文档测试行为:
|
||
|
||
```rust
|
||
/// ```
|
||
/// # use library::Book; // 这行在文档中隐藏,但测试时执行
|
||
/// let book = Book::new("Rust编程", "978-1234567890");
|
||
/// ```
|
||
///
|
||
/// ```no_run
|
||
/// // 编译但不运行的代码
|
||
/// let book = fetch_from_database("978-1234567890");
|
||
/// ```
|
||
///
|
||
/// ```compile_fail
|
||
/// // 应该编译失败的代码
|
||
/// let book: Book = "不是书";
|
||
/// ```
|
||
///
|
||
/// ```ignore
|
||
/// // 完全忽略的代码
|
||
/// let x = 非法语法;
|
||
/// ```
|
||
```
|
||
|
||
## 4.6 实例项目:图书馆管理系统
|
||
|
||
让我们应用所学知识,构建一个模块化的图书馆管理系统。
|
||
|
||
### 项目结构设计
|
||
|
||
```
|
||
library/
|
||
├── Cargo.toml
|
||
└── src/
|
||
├── lib.rs # 主模块和公共API
|
||
├── models/ # 数据模型
|
||
│ ├── mod.rs
|
||
│ ├── book.rs
|
||
│ └── user.rs
|
||
├── services/ # 业务逻辑
|
||
│ ├── mod.rs
|
||
│ ├── catalog.rs
|
||
│ └── lending.rs
|
||
└── utils/ # 工具函数
|
||
├── mod.rs
|
||
└── validation.rs
|
||
```
|
||
|
||
### 实现核心模块
|
||
|
||
**1. 主模块 (lib.rs)**
|
||
|
||
```rust
|
||
//! # 图书馆管理系统
|
||
//!
|
||
//! 一个模块化的图书馆管理系统,提供图书编目、用户管理和借阅服务。
|
||
|
||
pub mod models;
|
||
pub mod services;
|
||
mod utils; // 内部工具模块,不对外暴露
|
||
|
||
// 重新导出核心类型,简化用户导入
|
||
pub use models::{Book, User};
|
||
pub use services::{catalog::Catalog, lending::LendingService};
|
||
|
||
/// 系统版本号
|
||
pub const VERSION: &str = "0.1.0";
|
||
|
||
/// 创建一个新的图书馆实例
|
||
pub fn new_library() -> services::Library {
|
||
services::Library::new()
|
||
}
|
||
```
|
||
|
||
**2. 模型模块 (models/mod.rs)**
|
||
|
||
```rust
|
||
//! 系统中使用的数据模型
|
||
|
||
pub mod book;
|
||
pub mod user;
|
||
|
||
pub use book::Book;
|
||
pub use user::User;
|
||
```
|
||
|
||
**3. 图书模型 (models/book.rs)**
|
||
|
||
```rust
|
||
//! 图书相关的数据模型
|
||
|
||
use std::fmt;
|
||
|
||
/// 表示图书馆中的一本书
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct Book {
|
||
title: String,
|
||
author: String,
|
||
isbn: String,
|
||
available: bool,
|
||
}
|
||
|
||
impl Book {
|
||
/// 创建一本新书
|
||
///
|
||
/// # 示例
|
||
/// ```
|
||
/// use library::Book;
|
||
/// let book = Book::new("Rust编程艺术", "张三", "978-1234567890");
|
||
/// assert_eq!(book.title(), "Rust编程艺术");
|
||
/// assert!(book.is_available());
|
||
/// ```
|
||
pub fn new(title: &str, author: &str, isbn: &str) -> Self {
|
||
Self {
|
||
title: title.to_string(),
|
||
author: author.to_string(),
|
||
isbn: isbn.to_string(),
|
||
available: true,
|
||
}
|
||
}
|
||
|
||
/// 获取书名
|
||
pub fn title(&self) -> &str {
|
||
&self.title
|
||
}
|
||
|
||
/// 获取作者
|
||
pub fn author(&self) -> &str {
|
||
&self.author
|
||
}
|
||
|
||
/// 获取ISBN
|
||
pub fn isbn(&self) -> &str {
|
||
&self.isbn
|
||
}
|
||
|
||
/// 检查书是否可借
|
||
pub fn is_available(&self) -> bool {
|
||
self.available
|
||
}
|
||
|
||
/// 标记为已借出
|
||
pub(crate) fn mark_borrowed(&mut self) {
|
||
self.available = false;
|
||
}
|
||
|
||
/// 标记为已归还
|
||
pub(crate) fn mark_returned(&mut self) {
|
||
self.available = true;
|
||
}
|
||
}
|
||
|
||
impl fmt::Display for Book {
|
||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
write!(f, "{} by {} (ISBN: {})", self.title, self.author, self.isbn)
|
||
}
|
||
}
|
||
|
||
**4. 用户模型 (models/user.rs)**
|
||
|
||
```rust
|
||
//! 用户相关的数据模型
|
||
|
||
use std::fmt;
|
||
|
||
/// 用户ID类型
|
||
pub type UserId = u32;
|
||
|
||
/// 表示图书馆的一名用户
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct User {
|
||
id: UserId,
|
||
name: String,
|
||
email: String,
|
||
active: bool,
|
||
borrowed_books: Vec<String>, // 存储已借阅书籍的ISBN
|
||
}
|
||
|
||
impl User {
|
||
/// 创建一个新用户
|
||
///
|
||
/// # 示例
|
||
/// ```
|
||
/// use library::User;
|
||
/// let user = User::new(1, "李四", "lisi@example.com");
|
||
/// assert_eq!(user.name(), "李四");
|
||
/// assert!(user.is_active());
|
||
/// ```
|
||
pub fn new(id: UserId, name: &str, email: &str) -> Self {
|
||
Self {
|
||
id,
|
||
name: name.to_string(),
|
||
email: email.to_string(),
|
||
active: true,
|
||
borrowed_books: Vec::new(),
|
||
}
|
||
}
|
||
|
||
/// 获取用户ID
|
||
pub fn id(&self) -> UserId {
|
||
self.id
|
||
}
|
||
|
||
/// 获取用户名
|
||
pub fn name(&self) -> &str {
|
||
&self.name
|
||
}
|
||
|
||
/// 获取用户邮箱
|
||
pub fn email(&self) -> &str {
|
||
&self.email
|
||
}
|
||
|
||
/// 检查用户是否激活
|
||
pub fn is_active(&self) -> bool {
|
||
self.active
|
||
}
|
||
|
||
/// 获取用户已借阅的书籍ISBN列表
|
||
pub fn borrowed_books(&self) -> &[String] {
|
||
&self.borrowed_books
|
||
}
|
||
|
||
/// 记录用户借阅了一本书
|
||
pub(crate) fn borrow_book(&mut self, isbn: &str) {
|
||
self.borrowed_books.push(isbn.to_string());
|
||
}
|
||
|
||
/// 记录用户归还了一本书
|
||
pub(crate) fn return_book(&mut self, isbn: &str) -> bool {
|
||
if let Some(pos) = self.borrowed_books.iter().position(|b| b == isbn) {
|
||
self.borrowed_books.remove(pos);
|
||
true
|
||
} else {
|
||
false
|
||
}
|
||
}
|
||
|
||
/// 停用用户账户
|
||
pub(crate) fn deactivate(&mut self) {
|
||
self.active = false;
|
||
}
|
||
|
||
/// 重新激活用户账户
|
||
pub(crate) fn activate(&mut self) {
|
||
self.active = true;
|
||
}
|
||
}
|
||
|
||
impl fmt::Display for User {
|
||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||
write!(f, "{} (ID: {}, Email: {})", self.name, self.id, self.email)
|
||
}
|
||
}
|
||
```
|
||
|
||
**5. 服务模块 (services/mod.rs)**
|
||
|
||
```rust
|
||
//! 图书馆业务服务模块
|
||
|
||
pub mod catalog;
|
||
pub mod lending;
|
||
|
||
use std::collections::HashMap;
|
||
use crate::models::{Book, User, UserId};
|
||
|
||
/// 图书馆主服务
|
||
pub struct Library {
|
||
catalog: catalog::Catalog,
|
||
lending: lending::LendingService,
|
||
}
|
||
|
||
impl Library {
|
||
/// 创建一个新的图书馆实例
|
||
pub fn new() -> Self {
|
||
Self {
|
||
catalog: catalog::Catalog::new(),
|
||
lending: lending::LendingService::new(),
|
||
}
|
||
}
|
||
|
||
/// 获取目录服务
|
||
pub fn catalog(&self) -> &catalog::Catalog {
|
||
&self.catalog
|
||
}
|
||
|
||
/// 获取可变目录服务
|
||
pub fn catalog_mut(&mut self) -> &mut catalog::Catalog {
|
||
&mut self.catalog
|
||
}
|
||
|
||
/// 获取借阅服务
|
||
pub fn lending(&self) -> &lending::LendingService {
|
||
&self.lending
|
||
}
|
||
|
||
/// 获取可变借阅服务
|
||
pub fn lending_mut(&mut self) -> &mut lending::LendingService {
|
||
&mut self.lending
|
||
}
|
||
}
|
||
|
||
impl Default for Library {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
```
|
||
|
||
**6. 目录服务 (services/catalog.rs)**
|
||
|
||
```rust
|
||
//! 图书目录管理服务
|
||
|
||
use std::collections::HashMap;
|
||
use crate::models::Book;
|
||
use crate::utils::validation;
|
||
|
||
/// 图书目录错误类型
|
||
#[derive(Debug)]
|
||
pub enum CatalogError {
|
||
/// ISBN已存在
|
||
DuplicateIsbn(String),
|
||
/// ISBN不存在
|
||
IsbnNotFound(String),
|
||
/// ISBN格式无效
|
||
InvalidIsbn(String),
|
||
}
|
||
|
||
/// 图书目录服务
|
||
pub struct Catalog {
|
||
books: HashMap<String, Book>,
|
||
}
|
||
|
||
impl Catalog {
|
||
/// 创建一个新的目录服务
|
||
pub fn new() -> Self {
|
||
Self {
|
||
books: HashMap::new(),
|
||
}
|
||
}
|
||
|
||
/// 添加一本新书到目录
|
||
///
|
||
/// # 错误
|
||
/// 如果ISBN已存在或无效,返回错误
|
||
///
|
||
/// # 示例
|
||
/// ```
|
||
/// use library::{Book, services::catalog::Catalog};
|
||
///
|
||
/// let mut catalog = Catalog::new();
|
||
/// let book = Book::new("Rust编程", "张三", "978-1234567890");
|
||
///
|
||
/// // 首次添加成功
|
||
/// assert!(catalog.add_book(book.clone()).is_ok());
|
||
///
|
||
/// // 重复添加失败
|
||
/// assert!(catalog.add_book(book).is_err());
|
||
/// ```
|
||
pub fn add_book(&mut self, book: Book) -> Result<(), CatalogError> {
|
||
let isbn = book.isbn();
|
||
|
||
// 验证ISBN格式
|
||
if !validation::is_valid_isbn(isbn) {
|
||
return Err(CatalogError::InvalidIsbn(isbn.to_string()));
|
||
}
|
||
|
||
// 检查ISBN是否已存在
|
||
if self.books.contains_key(isbn) {
|
||
return Err(CatalogError::DuplicateIsbn(isbn.to_string()));
|
||
}
|
||
|
||
// 添加图书
|
||
self.books.insert(isbn.to_string(), book);
|
||
Ok(())
|
||
}
|
||
|
||
/// 根据ISBN查找图书
|
||
pub fn find_by_isbn(&self, isbn: &str) -> Option<&Book> {
|
||
self.books.get(isbn)
|
||
}
|
||
|
||
/// 根据ISBN查找可变图书引用
|
||
pub(crate) fn find_by_isbn_mut(&mut self, isbn: &str) -> Option<&mut Book> {
|
||
self.books.get_mut(isbn)
|
||
}
|
||
|
||
/// 根据标题查找图书
|
||
pub fn find_by_title(&self, title: &str) -> Vec<&Book> {
|
||
self.books.values()
|
||
.filter(|book| book.title().contains(title))
|
||
.collect()
|
||
}
|
||
|
||
/// 根据作者查找图书
|
||
pub fn find_by_author(&self, author: &str) -> Vec<&Book> {
|
||
self.books.values()
|
||
.filter(|book| book.author().contains(author))
|
||
.collect()
|
||
}
|
||
|
||
/// 获取所有图书
|
||
pub fn all_books(&self) -> Vec<&Book> {
|
||
self.books.values().collect()
|
||
}
|
||
|
||
/// 获取可用图书数量
|
||
pub fn available_count(&self) -> usize {
|
||
self.books.values()
|
||
.filter(|book| book.is_available())
|
||
.count()
|
||
}
|
||
|
||
/// 获取总图书数量
|
||
pub fn total_count(&self) -> usize {
|
||
self.books.len()
|
||
}
|
||
}
|
||
|
||
impl Default for Catalog {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
```
|
||
|
||
**7. 借阅服务 (services/lending.rs)**
|
||
|
||
```rust
|
||
//! 图书借阅管理服务
|
||
|
||
use std::collections::HashMap;
|
||
use crate::models::{Book, User, UserId};
|
||
use crate::services::catalog::Catalog;
|
||
|
||
/// 借阅记录
|
||
#[derive(Debug)]
|
||
pub struct LoanRecord {
|
||
user_id: UserId,
|
||
isbn: String,
|
||
loan_date: chrono::DateTime<chrono::Utc>,
|
||
due_date: chrono::DateTime<chrono::Utc>,
|
||
}
|
||
|
||
/// 借阅错误类型
|
||
#[derive(Debug)]
|
||
pub enum LendingError {
|
||
/// 用户不存在
|
||
UserNotFound(UserId),
|
||
/// 图书不存在
|
||
BookNotFound(String),
|
||
/// 图书不可用(已借出)
|
||
BookUnavailable(String),
|
||
/// 用户账户未激活
|
||
UserInactive(UserId),
|
||
/// 用户已达到最大借阅限制
|
||
BorrowLimitReached(UserId),
|
||
/// 借阅记录不存在
|
||
LoanNotFound,
|
||
}
|
||
|
||
/// 借阅服务
|
||
pub struct LendingService {
|
||
users: HashMap<UserId, User>,
|
||
active_loans: Vec<LoanRecord>,
|
||
max_loans_per_user: usize,
|
||
loan_duration_days: i64,
|
||
}
|
||
|
||
impl LendingService {
|
||
/// 创建一个新的借阅服务
|
||
pub fn new() -> Self {
|
||
Self {
|
||
users: HashMap::new(),
|
||
active_loans: Vec::new(),
|
||
max_loans_per_user: 5,
|
||
loan_duration_days: 14,
|
||
}
|
||
}
|
||
|
||
/// 设置每位用户的最大借阅数量
|
||
pub fn set_max_loans_per_user(&mut self, max: usize) {
|
||
self.max_loans_per_user = max;
|
||
}
|
||
|
||
/// 设置借阅期限(天数)
|
||
pub fn set_loan_duration(&mut self, days: i64) {
|
||
self.loan_duration_days = days;
|
||
}
|
||
|
||
/// 添加用户
|
||
pub fn add_user(&mut self, user: User) {
|
||
self.users.insert(user.id(), user);
|
||
}
|
||
|
||
/// 查找用户
|
||
pub fn find_user(&self, user_id: UserId) -> Option<&User> {
|
||
self.users.get(&user_id)
|
||
}
|
||
|
||
/// 查找可变用户引用
|
||
fn find_user_mut(&mut self, user_id: UserId) -> Option<&mut User> {
|
||
self.users.get_mut(&user_id)
|
||
}
|
||
|
||
/// 借阅图书
|
||
pub fn borrow_book(
|
||
&mut self,
|
||
user_id: UserId,
|
||
isbn: &str,
|
||
catalog: &mut Catalog
|
||
) -> Result<(), LendingError> {
|
||
// 检查用户是否存在
|
||
let user = self.find_user_mut(user_id)
|
||
.ok_or(LendingError::UserNotFound(user_id))?;
|
||
|
||
// 检查用户是否激活
|
||
if !user.is_active() {
|
||
return Err(LendingError::UserInactive(user_id));
|
||
}
|
||
|
||
// 检查用户是否达到借阅上限
|
||
if user.borrowed_books().len() >= self.max_loans_per_user {
|
||
return Err(LendingError::BorrowLimitReached(user_id));
|
||
}
|
||
|
||
// 检查图书是否存在并可借
|
||
let book = catalog.find_by_isbn_mut(isbn)
|
||
.ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?;
|
||
|
||
if !book.is_available() {
|
||
return Err(LendingError::BookUnavailable(isbn.to_string()));
|
||
}
|
||
|
||
// 更新图书状态
|
||
book.mark_borrowed();
|
||
|
||
// 更新用户借阅记录
|
||
user.borrow_book(isbn);
|
||
|
||
// 创建借阅记录
|
||
let now = chrono::Utc::now();
|
||
let due_date = now + chrono::Duration::days(self.loan_duration_days);
|
||
|
||
self.active_loans.push(LoanRecord {
|
||
user_id,
|
||
isbn: isbn.to_string(),
|
||
loan_date: now,
|
||
due_date,
|
||
});
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 归还图书
|
||
pub fn return_book(
|
||
&mut self,
|
||
user_id: UserId,
|
||
isbn: &str,
|
||
catalog: &mut Catalog
|
||
) -> Result<(), LendingError> {
|
||
// 检查用户是否存在
|
||
let user = self.find_user_mut(user_id)
|
||
.ok_or(LendingError::UserNotFound(user_id))?;
|
||
|
||
// 检查图书是否存在
|
||
let book = catalog.find_by_isbn_mut(isbn)
|
||
.ok_or_else(|| LendingError::BookNotFound(isbn.to_string()))?;
|
||
|
||
// 更新用户借阅记录
|
||
if !user.return_book(isbn) {
|
||
return Err(LendingError::LoanNotFound);
|
||
}
|
||
|
||
// 更新图书状态
|
||
book.mark_returned();
|
||
|
||
// 移除借阅记录
|
||
let loan_index = self.active_loans.iter()
|
||
.position(|loan| loan.user_id == user_id && loan.isbn == isbn)
|
||
.ok_or(LendingError::LoanNotFound)?;
|
||
|
||
self.active_loans.remove(loan_index);
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 获取用户的所有借阅
|
||
pub fn get_user_loans(&self, user_id: UserId) -> Vec<&LoanRecord> {
|
||
self.active_loans.iter()
|
||
.filter(|loan| loan.user_id == user_id)
|
||
.collect()
|
||
}
|
||
|
||
/// 获取所有逾期借阅
|
||
pub fn get_overdue_loans(&self) -> Vec<&LoanRecord> {
|
||
let now = chrono::Utc::now();
|
||
self.active_loans.iter()
|
||
.filter(|loan| loan.due_date < now)
|
||
.collect()
|
||
}
|
||
}
|
||
|
||
impl Default for LendingService {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
```
|
||
|
||
**8. 工具模块 (utils/mod.rs)**
|
||
|
||
```rust
|
||
//! 内部工具函数
|
||
|
||
pub mod validation;
|
||
```
|
||
|
||
**9. 验证工具 (utils/validation.rs)**
|
||
|
||
```rust
|
||
//! 数据验证工具
|
||
|
||
/// 检查ISBN是否有效
|
||
///
|
||
/// 简化版实现,仅检查基本格式
|
||
pub fn is_valid_isbn(isbn: &str) -> bool {
|
||
// 移除所有连字符和空格
|
||
let isbn = isbn.replace(['-', ' '], "");
|
||
|
||
// ISBN-13应为13位数字
|
||
if isbn.len() != 13 {
|
||
return false;
|
||
}
|
||
|
||
// 检查是否全为数字
|
||
isbn.chars().all(|c| c.is_ascii_digit())
|
||
}
|
||
|
||
/// 检查邮箱格式是否有效
|
||
pub fn is_valid_email(email: &str) -> bool {
|
||
// 简化版邮箱验证
|
||
email.contains('@') && email.split('@').count() == 2
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_valid_isbn() {
|
||
assert!(is_valid_isbn("978-1234567890"));
|
||
assert!(is_valid_isbn("9781234567890"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_invalid_isbn() {
|
||
assert!(!is_valid_isbn("978-12345")); // 太短
|
||
assert!(!is_valid_isbn("978-1234567890X")); // 包含非数字
|
||
}
|
||
|
||
#[test]
|
||
fn test_valid_email() {
|
||
assert!(is_valid_email("user@example.com"));
|
||
}
|
||
|
||
#[test]
|
||
fn test_invalid_email() {
|
||
assert!(!is_valid_email("user@"));
|
||
assert!(!is_valid_email("user"));
|
||
assert!(!is_valid_email("user@domain@example"));
|
||
}
|
||
}
|
||
```
|
||
|
||
### 示例应用程序
|
||
|
||
让我们创建一个简单的命令行应用程序,展示如何使用我们的图书馆管理系统:
|
||
|
||
**10. 主程序 (main.rs)**
|
||
|
||
```rust
|
||
//! 图书馆管理系统示例应用
|
||
|
||
use library::{Book, User, new_library};
|
||
|
||
fn main() {
|
||
println!("=== 图书馆管理系统 ===");
|
||
|
||
// 创建图书馆实例
|
||
let mut library = new_library();
|
||
|
||
// 添加图书
|
||
let books = [
|
||
Book::new("Rust编程艺术", "张三", "978-1234567890"),
|
||
Book::new("系统设计实践", "李四", "978-0987654321"),
|
||
Book::new("数据结构与算法", "王五", "978-5432109876"),
|
||
];
|
||
|
||
for book in &books {
|
||
match library.catalog_mut().add_book(book.clone()) {
|
||
Ok(_) => println!("添加图书: {}", book),
|
||
Err(e) => println!("添加图书失败: {:?}", e),
|
||
}
|
||
}
|
||
|
||
// 添加用户
|
||
let users = [
|
||
User::new(1, "赵六", "zhaoliu@example.com"),
|
||
User::new(2, "钱七", "qianqi@example.com"),
|
||
];
|
||
|
||
for user in &users {
|
||
library.lending_mut().add_user(user.clone());
|
||
println!("添加用户: {}", user);
|
||
}
|
||
|
||
// 借阅图书
|
||
let user_id = 1;
|
||
let isbn = "978-1234567890";
|
||
|
||
match library.lending_mut().borrow_book(user_id, isbn, library.catalog_mut()) {
|
||
Ok(_) => println!("用户 {} 成功借阅图书 {}", user_id, isbn),
|
||
Err(e) => println!("借阅失败: {:?}", e),
|
||
}
|
||
|
||
// 查看用户借阅情况
|
||
if let Some(user) = library.lending().find_user(user_id) {
|
||
println!("用户 {} 已借阅的图书:", user.name());
|
||
for isbn in user.borrowed_books() {
|
||
if let Some(book) = library.catalog().find_by_isbn(isbn) {
|
||
println!(" - {}", book);
|
||
}
|
||
}
|
||
}
|
||
|
||
// 归还图书
|
||
match library.lending_mut().return_book(user_id, isbn, library.catalog_mut()) {
|
||
Ok(_) => println!("用户 {} 成功归还图书 {}", user_id, isbn),
|
||
Err(e) => println!("归还失败: {:?}", e),
|
||
}
|
||
|
||
// 查看图书馆统计信息
|
||
println!("\n=== 图书馆统计 ===");
|
||
println!("总图书数: {}", library.catalog().total_count());
|
||
println!("可借图书数: {}", library.catalog().available_count());
|
||
}
|
||
```
|
||
|
||
### 项目配置
|
||
|
||
**11. 包配置 (Cargo.toml)**
|
||
|
||
```toml
|
||
[package]
|
||
name = "library"
|
||
version = "0.1.0"
|
||
edition = "2021"
|
||
authors = ["Your Name <your.email@example.com>"]
|
||
description = "A modular library management system"
|
||
|
||
[dependencies]
|
||
chrono = "0.4"
|
||
|
||
[dev-dependencies]
|
||
criterion = "0.5"
|
||
|
||
[lib]
|
||
name = "library"
|
||
path = "src/lib.rs"
|
||
|
||
[[bin]]
|
||
name = "library-cli"
|
||
path = "src/main.rs"
|
||
```
|
||
|
||
## 4.7 总结
|
||
|
||
在本章中,我们探索了Rust的模块系统,学习了如何组织和结构化大型项目。我们讨论了以下关键概念:
|
||
|
||
1. **模块层级设计**:使用`mod`关键字创建模块树,通过文件系统组织代码
|
||
2. **可见性控制**:使用`pub`及其变体控制API的暴露范围
|
||
3. **工作空间管理**:使用工作空间组织多个相关crate
|
||
4. **文档测试**:编写可执行的文档,确保代码示例始终有效
|
||
|
||
通过图书馆管理系统的实例项目,我们实践了这些概念,构建了一个模块化、可维护的系统。这种结构化方法不仅提高了代码的可读性和可维护性,还促进了团队协作和代码重用。
|
||
|
||
### 关键要点回顾
|
||
|
||
- 使用模块组织相关功能,形成清晰的代码结构
|
||
- 遵循"最小公开原则",只暴露必要的API
|
||
- 利用文件系统组织模块,使代码结构直观
|
||
- 使用工作空间管理大型项目中的多个crate
|
||
- 编写文档测试,确保文档与代码同步
|
||
|
||
### 实践建议
|
||
|
||
1. **模块设计**:先设计模块结构,再编写实现代码
|
||
2. **API设计**:谨慎考虑哪些项应该公开,哪些应该保持私有
|
||
3. **文档优先**:编写文档和测试,然后实现功能
|
||
4. **渐进式重构**:随着项目增长,不断调整模块结构
|
||
|
||
在下一章中,我们将探索Rust的并发安全特性,学习如何利用所有权系统编写高效、安全的并发代码。 |