引言

代码组织是软件工程中最被低估却最为重要的主题之一。随着项目规模增长,缺乏良好组织的代码库会变得难以理解、维护和扩展——查找功能需要在数千行代码中搜索、修改一处影响未知的多处、新成员无从下手。Rust 的模块系统提供了强大的代码组织能力,从文件级的 mod 声明到 crate 级的 workspace,从可见性控制到依赖管理,从单体应用到微服务架构。理解模块系统的层次结构、掌握 pub 可见性的细粒度控制、学会使用 workspace 管理多 crate 项目、实践领域驱动设计的模块划分,是构建可维护大型 Rust 项目的核心技能。本文深入探讨 Rust 代码组织的原则、模式、工具和最佳实践,揭示如何在代码规模、团队协作和系统复杂度增长时保持架构清晰。

模块系统的层次结构

Rust 的模块系统是树形的多层结构。最顶层是 crate,分为 binary crate(可执行程序)和 library crate(库)。每个 crate 有一个根模块(main.rslib.rs),根模块下可以声明子模块。模块既可以内联定义在同一文件中,也可以分离到独立文件或目录。这种灵活性允许从小项目的单文件到大项目的多层目录结构。

文件系统映射是 Rust 2018 后的重要改进。声明 mod foo; 会查找 foo.rsfoo/mod.rs。后者适合模块本身需要子模块的情况——foo/mod.rs 定义模块,foo/bar.rs 是其子模块。这种自然映射消除了旧版本中的 mod.rs 混乱,让项目结构更清晰。

模块路径使用 :: 连接。绝对路径从 crate 根开始(crate::module::function),相对路径从当前模块开始(super::siblingself::child)。use 语句简化路径引用,支持别名、重导出、通配符导入。但应该谨慎使用通配符——它降低了代码可读性,隐藏了依赖关系。

可见性控制的策略

Rust 默认私有的可见性是封装的基础。只有标记为 pub 的项才能被外部访问,这强制了清晰的公共 API 边界。但 pub 不是二元的——Rust 提供了细粒度的可见性控制。

pub(crate) 使项在整个 crate 内可见但对外部 crate 隐藏。这适合内部工具函数、crate 间共享但不想暴露的实现细节。pub(super) 限制可见性到父模块,pub(in path) 指定特定祖先模块。这些细粒度控制允许在不破坏封装的前提下实现跨模块协作。

重导出(re-export)是 API 设计的重要工具。库的内部组织可能很复杂,但公共 API 应该扁平、简洁。通过在根模块 use pub 重导出关键类型和函数,隐藏内部结构,为用户提供清晰的入口。标准库广泛使用这种模式——std::collections::HashMap 实际定义在深层模块中,但通过重导出提供简单路径。

结构体字段的可见性也可以独立控制。公共结构体可以有私有字段,强制通过构造函数和方法访问,保证不变量。这是面向对象封装在 Rust 中的实现——类型是公开的,但实现细节隐藏。

Workspace 与多 crate 架构

随着项目增长,单一 crate 变得难以管理。Workspace 允许将项目分解为多个相关 crate,共享 Cargo.locktarget 目录,统一版本管理。这种模块化有多重好处——编译并行化、依赖隔离、职责清晰、复用方便。

典型的 workspace 结构将功能分解为独立 crate。核心业务逻辑在 core crate,HTTP API 在 api crate,CLI 在 cli crate,共享工具在 utils crate。每个 crate 专注单一职责,清晰的依赖关系防止循环依赖和紧耦合。新功能可以作为新 crate 添加,而不影响现有代码。

依赖管理在 workspace 层面统一。在根 Cargo.toml[workspace.dependencies] 定义共同依赖的版本,成员 crate 通过 workspace = true 继承。这确保了版本一致性,避免了依赖地狱。但应该避免过度共享——不同 crate 可能需要不同版本的依赖。

内部 crate 的可见性通过依赖关系控制。只在需要时添加 crate 依赖,避免不必要的耦合。使用 pub(crate) 限制内部实现细节,只导出必要的公共 API。这种分层设计让大型项目保持可管理。

领域驱动的模块划分

模块划分应该反映问题域而非技术分层。按业务能力组织——用户管理、订单处理、支付、通知——而不是按技术角色——模型、控制器、服务。这种垂直划分让相关代码聚集,减少了跨模块依赖,提高了内聚性。

每个领域模块应该是自包含的,有清晰的输入输出接口。模块间通过明确定义的 API 通信,而不是直接访问内部数据结构。这种松耦合让模块可以独立演进、测试和替换。依赖注入、trait 抽象、消息传递都是实现解耦的工具。

共享内核(shared kernel)包含跨领域的通用类型和函数——错误类型、配置、工具函数。但共享内核应该保持最小——过度共享导致紧耦合。可以考虑为每个领域定义自己的类型,即使概念相似,避免跨领域泄露实现细节。

反腐败层(anti-corruption layer)隔离外部依赖。将第三方库、数据库、外部 API 封装在适配器层,领域逻辑依赖抽象接口而非具体实现。这让外部变化不会波及核心业务逻辑,也方便测试时替换为 mock。

深度实践:大型项目的模块化架构

# Cargo.toml - Workspace 根配置

[workspace]
members = [
    "crates/core",
    "crates/api",
    "crates/cli",
    "crates/utils",
]
resolver = "2"

[workspace.dependencies]
# 共享依赖
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
anyhow = "1.0"
thiserror = "1.0"

[workspace.package]
version = "0.1.0"
edition = "2021"
authors = ["Your Name"]
# crates/core/Cargo.toml

[package]
name = "myapp-core"
version.workspace = true
edition.workspace = true

[dependencies]
serde.workspace = true
thiserror.workspace = true
// crates/core/src/lib.rs

//! 核心业务逻辑模块

// 领域模块
pub mod user;
pub mod order;
pub mod payment;

// 共享类型
pub mod error;
pub mod config;

// 重导出关键类型
pub use error::{Error, Result};
pub use config::Config;

/// Crate 级别的内部工具(只在 core 内可见)
pub(crate) mod internal_utils {
    pub fn validate_email(email: &str) -> bool {
        email.contains('@')
    }
}
// crates/core/src/error.rs

//! 统一错误处理

use thiserror::Error;

#[derive(Error, Debug)]
pub enum Error {
    #[error("用户错误: {0}")]
    User(#[from] crate::user::UserError),

    #[error("订单错误: {0}")]
    Order(#[from] crate::order::OrderError),

    #[error("支付错误: {0}")]
    Payment(#[from] crate::payment::PaymentError),

    #[error("配置错误: {0}")]
    Config(String),

    #[error("内部错误: {0}")]
    Internal(String),
}

pub type Result<T> = std::result::Result<T, Error>;
// crates/core/src/user/mod.rs

//! 用户领域模块

mod model;
mod repository;
mod service;

// 公开导出
pub use model::{User, UserId};
pub use service::UserService;

// 内部使用
pub(crate) use repository::UserRepository;

use thiserror::Error;

#[derive(Error, Debug)]
pub enum UserError {
    #[error("用户未找到: {0}")]
    NotFound(UserId),

    #[error("用户已存在: {0}")]
    AlreadyExists(String),

    #[error("无效的邮箱: {0}")]
    InvalidEmail(String),
}
// crates/core/src/user/model.rs

use serde::{Deserialize, Serialize};

/// 用户 ID(强类型)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UserId(pub u64);

/// 用户实体
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    id: UserId,
    email: String,
    name: String,
    // 私有字段强制通过方法访问
    created_at: i64,
}

impl User {
    /// 构造函数(验证不变量)
    pub fn new(id: UserId, email: String, name: String) -> Result<Self, super::UserError> {
        if !crate::internal_utils::validate_email(&email) {
            return Err(super::UserError::InvalidEmail(email));
        }

        Ok(Self {
            id,
            email,
            name,
            created_at: current_timestamp(),
        })
    }

    // 访问器
    pub fn id(&self) -> UserId {
        self.id
    }

    pub fn email(&self) -> &str {
        &self.email
    }

    pub fn name(&self) -> &str {
        &self.name
    }
}

fn current_timestamp() -> i64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs() as i64
}
// crates/core/src/user/repository.rs

//! 用户仓储(数据访问抽象)

use super::{User, UserId, UserError};
use crate::Result;

/// 用户仓储 trait(依赖倒置)
pub trait UserRepository: Send + Sync {
    fn find_by_id(&self, id: UserId) -> Result<Option<User>>;
    fn find_by_email(&self, email: &str) -> Result<Option<User>>;
    fn save(&self, user: &User) -> Result<()>;
    fn delete(&self, id: UserId) -> Result<()>;
}

/// 内存实现(用于测试)
pub struct InMemoryUserRepository {
    users: std::sync::RwLock<std::collections::HashMap<UserId, User>>,
}

impl InMemoryUserRepository {
    pub fn new() -> Self {
        Self {
            users: std::sync::RwLock::new(std::collections::HashMap::new()),
        }
    }
}

impl UserRepository for InMemoryUserRepository {
    fn find_by_id(&self, id: UserId) -> Result<Option<User>> {
        let users = self.users.read().unwrap();
        Ok(users.get(&id).cloned())
    }

    fn find_by_email(&self, email: &str) -> Result<Option<User>> {
        let users = self.users.read().unwrap();
        Ok(users.values().find(|u| u.email() == email).cloned())
    }

    fn save(&self, user: &User) -> Result<()> {
        let mut users = self.users.write().unwrap();
        users.insert(user.id(), user.clone());
        Ok(())
    }

    fn delete(&self, id: UserId) -> Result<()> {
        let mut users = self.users.write().unwrap();
        users.remove(&id);
        Ok(())
    }
}
// crates/core/src/user/service.rs

//! 用户服务(业务逻辑)

use super::{User, UserId, UserError, UserRepository};
use crate::Result;
use std::sync::Arc;

/// 用户服务
pub struct UserService {
    repository: Arc<dyn UserRepository>,
}

impl UserService {
    pub fn new(repository: Arc<dyn UserRepository>) -> Self {
        Self { repository }
    }

    /// 创建用户
    pub fn create_user(&self, email: String, name: String) -> Result<User> {
        // 检查重复
        if let Some(_) = self.repository.find_by_email(&email)? {
            return Err(UserError::AlreadyExists(email).into());
        }

        // 创建用户
        let id = UserId(generate_id());
        let user = User::new(id, email, name)?;

        // 保存
        self.repository.save(&user)?;

        Ok(user)
    }

    /// 获取用户
    pub fn get_user(&self, id: UserId) -> Result<User> {
        self.repository
            .find_by_id(id)?
            .ok_or_else(|| UserError::NotFound(id).into())
    }

    /// 删除用户
    pub fn delete_user(&self, id: UserId) -> Result<()> {
        self.repository.delete(id)
    }
}

fn generate_id() -> u64 {
    use std::sync::atomic::{AtomicU64, Ordering};
    static COUNTER: AtomicU64 = AtomicU64::new(1);
    COUNTER.fetch_add(1, Ordering::Relaxed)
}
# crates/api/Cargo.toml

[package]
name = "myapp-api"
version.workspace = true
edition.workspace = true

[dependencies]
myapp-core = { path = "../core" }
tokio.workspace = true
serde.workspace = true
anyhow.workspace = true

# API 特定依赖
axum = "0.7"
tower = "0.4"
// crates/api/src/lib.rs

//! HTTP API 模块

use myapp_core::{Config, user::UserService};
use std::sync::Arc;

pub mod routes;
pub mod handlers;

/// API 服务器状态
pub struct AppState {
    pub user_service: Arc<UserService>,
    pub config: Arc<Config>,
}

impl AppState {
    pub fn new(user_service: Arc<UserService>, config: Arc<Config>) -> Self {
        Self {
            user_service,
            config,
        }
    }
}
// crates/api/src/handlers/user.rs

//! 用户 API 处理器

use axum::{Json, extract::{State, Path}};
use serde::{Deserialize, Serialize};
use myapp_core::user::{UserId, User};
use crate::AppState;

#[derive(Deserialize)]
pub struct CreateUserRequest {
    pub email: String,
    pub name: String,
}

#[derive(Serialize)]
pub struct UserResponse {
    pub id: u64,
    pub email: String,
    pub name: String,
}

impl From<User> for UserResponse {
    fn from(user: User) -> Self {
        Self {
            id: user.id().0,
            email: user.email().to_string(),
            name: user.name().to_string(),
        }
    }
}

/// 创建用户
pub async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(req): Json<CreateUserRequest>,
) -> Result<Json<UserResponse>, String> {
    let user = state.user_service
        .create_user(req.email, req.name)
        .map_err(|e| e.to_string())?;
    
    Ok(Json(user.into()))
}

/// 获取用户
pub async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u64>,
) -> Result<Json<UserResponse>, String> {
    let user = state.user_service
        .get_user(UserId(id))
        .map_err(|e| e.to_string())?;
    
    Ok(Json(user.into()))
}

实践中的专业思考

按领域而非技术分层:将相关功能组织在一起,而不是将所有模型放一起、所有服务放一起。这提高了内聚性,减少了跨模块依赖。

最小化公共 API:只导出必要的类型和函数。使用 pub(crate) 共享内部实现,保持外部 API 简洁。重构内部结构不应影响外部用户。

依赖倒置原则:高层模块依赖抽象(trait)而非具体实现。这让模块可测试、可替换、可扩展。

避免循环依赖:如果 A 依赖 B 且 B 依赖 A,说明模块划分有问题。应该提取共享部分到第三个模块,或重新设计接口。

workspace 的合理使用:不要过早拆分 crate——小项目单 crate 足够。当模块职责清晰、编译时间长、需要独立版本时才拆分。

文档是 API 的一部分:公共模块、类型、函数都应该有文档注释。cargo doc 生成的文档是用户理解代码的主要途径。

结语

代码组织是软件工程的艺术,它在灵活性和约束、简单性和可扩展性之间寻找平衡。Rust 的模块系统提供了强大的工具——从文件级模块到 workspace、从可见性控制到 trait 抽象——让我们能够构建清晰、可维护的大型项目。掌握这些技术,不仅能编写更好的代码,更能在团队协作和系统演进中保持架构清晰。这正是专业软件工程的体现——通过精心的组织和设计,让复杂系统保持简单、可理解、可维护。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐