引言

随着程序规模的增长,代码组织变得越来越重要。今天我们将学习Rust的模块系统,它帮助我们组织代码、控制作用域和管理私有性。

一、模块基础

1.1 什么是模块?

模块是Rust中组织代码的方式,可以:

  • 将相关的代码组织在一起
  • 控制代码的可见性(公开/私有)
  • 创建命名空间

1.2 定义模块

使用mod关键字定义模块:

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
        fn seat_at_table() {}
    }
    
    mod serving {
        fn take_order() {}
        fn serve_order() {}
        fn take_payment() {}
    }
}

fn main() {
    // 模块内的函数默认是私有的
}

1.3 模块树

crate
 └── front_of_house
      ├── hosting
      │    ├── add_to_waitlist
      │    └── seat_at_table
      └── serving
           ├── take_order
           ├── serve_order
           └── take_payment

二、路径

2.1 绝对路径和相对路径

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("添加到等待列表");
        }
    }
}

pub fn eat_at_restaurant() {
    // 绝对路径
    crate::front_of_house::hosting::add_to_waitlist();
    
    // 相对路径
    front_of_house::hosting::add_to_waitlist();
}

fn main() {
    eat_at_restaurant();
}

路径规则

  • 绝对路径:从crate根开始,使用crate
  • 相对路径:从当前模块开始

2.2 pub关键字

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
        
        fn seat_at_table() {}  // 私有函数
    }
}

pub fn eat_at_restaurant() {
    front_of_house::hosting::add_to_waitlist();  // ✅ 可以
    // front_of_house::hosting::seat_at_table();  // ❌ 私有
}

fn main() {
    eat_at_restaurant();
}

2.3 super关键字

使用super访问父模块:

fn serve_order() {
    println!("上菜");
}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();  // 调用父模块的函数
    }
    
    fn cook_order() {
        println!("烹饪");
    }
}

fn main() {}

三、结构体和枚举的可见性

3.1 公开结构体

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("桃子"),
            }
        }
    }
}

fn main() {
    let mut meal = back_of_house::Breakfast::summer("黑麦");
    meal.toast = String::from("小麦");  // ✅ 可以修改公开字段
    println!("我想要{}面包", meal.toast);
    
    // meal.seasonal_fruit = String::from("蓝莓");  // ❌ 私有字段
}

3.2 公开枚举

mod back_of_house {
    pub enum Appetizer {
        Soup,
        Salad,
    }
}

fn main() {
    // 枚举的所有变体都是公开的
    let order1 = back_of_house::Appetizer::Soup;
    let order2 = back_of_house::Appetizer::Salad;
}

四、use关键字

4.1 基本使用

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("添加到等待列表");
        }
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();  // 简化调用
}

fn main() {
    eat_at_restaurant();
}

4.2 use的惯用方式

use std::collections::HashMap;  // ✅ 对于类型
use std::fmt::Result;
use std::io::Result as IoResult;  // 重命名避免冲突

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}

4.3 嵌套路径

// ❌ 繁琐的方式
// use std::io;
// use std::io::Write;
// use std::cmp::Ordering;

// ✅ 使用嵌套路径
use std::{cmp::Ordering, io::{self, Write}};

fn main() {
    println!("使用嵌套路径简化导入");
}

4.4 glob运算符

use std::collections::*;  // 导入所有公开项

fn main() {
    let mut map = HashMap::new();
    let mut set = HashSet::new();
}

注意:glob运算符会使代码难以理解,一般只在测试模块中使用。

五、将模块分割到不同文件

5.1 项目结构

restaurant/
├── Cargo.toml
└── src/
    ├── main.rs
    ├── front_of_house.rs
    └── front_of_house/
        ├── hosting.rs
        └── serving.rs

5.2 main.rs

mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}

fn main() {
    eat_at_restaurant();
}

5.3 front_of_house.rs

pub mod hosting;
pub mod serving;

5.4 front_of_house/hosting.rs

pub fn add_to_waitlist() {
    println!("添加到等待列表");
}

pub fn seat_at_table() {
    println!("安排座位");
}

5.5 front_of_house/serving.rs

pub fn take_order() {
    println!("点餐");
}

pub fn serve_order() {
    println!("上菜");
}

pub fn take_payment() {
    println!("结账");
}

六、实战示例

示例1:数学库

项目结构:

math_lib/
├── Cargo.toml
└── src/
    ├── lib.rs
    ├── geometry.rs
    ├── geometry/
    │   ├── circle.rs
    │   └── rectangle.rs
    └── statistics.rs

src/lib.rs:

pub mod geometry;
pub mod statistics;

pub fn version() -> &'static str {
    "1.0.0"
}

src/geometry.rs:

pub mod circle;
pub mod rectangle;

pub trait Shape {
    fn area(&self) -> f64;
    fn perimeter(&self) -> f64;
}

src/geometry/circle.rs:

use std::f64::consts::PI;
use super::Shape;

pub struct Circle {
    radius: f64,
}

impl Circle {
    pub fn new(radius: f64) -> Circle {
        Circle { radius }
    }
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        PI * self.radius * self.radius
    }
    
    fn perimeter(&self) -> f64 {
        2.0 * PI * self.radius
    }
}

src/geometry/rectangle.rs:

use super::Shape;

pub struct Rectangle {
    width: f64,
    height: f64,
}

impl Rectangle {
    pub fn new(width: f64, height: f64) -> Rectangle {
        Rectangle { width, height }
    }
    
    pub fn is_square(&self) -> bool {
        self.width == self.height
    }
}

impl Shape for Rectangle {
    fn area(&self) -> f64 {
        self.width * self.height
    }
    
    fn perimeter(&self) -> f64 {
        2.0 * (self.width + self.height)
    }
}

src/statistics.rs:

pub fn mean(numbers: &[f64]) -> f64 {
    if numbers.is_empty() {
        return 0.0;
    }
    
    let sum: f64 = numbers.iter().sum();
    sum / numbers.len() as f64
}

pub fn median(numbers: &mut [f64]) -> f64 {
    if numbers.is_empty() {
        return 0.0;
    }
    
    numbers.sort_by(|a, b| a.partial_cmp(b).unwrap());
    let mid = numbers.len() / 2;
    
    if numbers.len() % 2 == 0 {
        (numbers[mid - 1] + numbers[mid]) / 2.0
    } else {
        numbers[mid]
    }
}

pub fn mode(numbers: &[i32]) -> Option<i32> {
    use std::collections::HashMap;
    
    let mut counts = HashMap::new();
    
    for &num in numbers {
        *counts.entry(num).or_insert(0) += 1;
    }
    
    counts
        .into_iter()
        .max_by_key(|&(_, count)| count)
        .map(|(num, _)| num)
}

使用库:

use math_lib::geometry::{Shape, circle::Circle, rectangle::Rectangle};
use math_lib::statistics;

fn main() {
    let circle = Circle::new(5.0);
    println!("圆的面积: {:.2}", circle.area());
    
    let rect = Rectangle::new(10.0, 5.0);
    println!("矩形的面积: {:.2}", rect.area());
    println!("是正方形: {}", rect.is_square());
    
    let numbers = vec![1.0, 2.0, 3.0, 4.0, 5.0];
    println!("平均值: {:.2}", statistics::mean(&numbers));
    
    let mut numbers2 = vec![5.0, 2.0, 8.0, 1.0, 9.0];
    println!("中位数: {:.2}", statistics::median(&mut numbers2));
}

示例2:Web服务器模块

项目结构:

web_server/
├── Cargo.toml
└── src/
    ├── main.rs
    ├── server.rs
    ├── router.rs
    ├── handlers/
    │   ├── mod.rs
    │   ├── api.rs
    │   └── static_files.rs
    └── middleware/
        ├── mod.rs
        ├── logger.rs
        └── auth.rs

src/server.rs:

use std::net::TcpListener;

pub struct Server {
    address: String,
}

impl Server {
    pub fn new(address: &str) -> Server {
        Server {
            address: address.to_string(),
        }
    }
    
    pub fn run(&self) {
        println!("服务器运行在 {}", self.address);
        
        let listener = TcpListener::bind(&self.address)
            .expect("无法绑定地址");
        
        for stream in listener.incoming() {
            match stream {
                Ok(_stream) => {
                    println!("新连接");
                }
                Err(e) => {
                    eprintln!("连接错误: {}", e);
                }
            }
        }
    }
}

src/router.rs:

use std::collections::HashMap;

pub type Handler = fn(&str) -> String;

pub struct Router {
    routes: HashMap<String, Handler>,
}

impl Router {
    pub fn new() -> Router {
        Router {
            routes: HashMap::new(),
        }
    }
    
    pub fn add_route(&mut self, path: &str, handler: Handler) {
        self.routes.insert(path.to_string(), handler);
    }
    
    pub fn route(&self, path: &str) -> Option<&Handler> {
        self.routes.get(path)
    }
}

src/handlers/mod.rs:

pub mod api;
pub mod static_files;

pub fn not_found() -> String {
    String::from("404 Not Found")
}

src/handlers/api.rs:

pub fn health_check(_req: &str) -> String {
    String::from(r#"{"status": "ok"}"#)
}

pub fn get_users(_req: &str) -> String {
    String::from(r#"{"users": []}"#)
}

src/middleware/mod.rs:

pub mod logger;
pub mod auth;

pub trait Middleware {
    fn process(&self, request: &str) -> String;
}

src/middleware/logger.rs:

use super::Middleware;

pub struct Logger;

impl Logger {
    pub fn new() -> Logger {
        Logger
    }
}

impl Middleware for Logger {
    fn process(&self, request: &str) -> String {
        println!("[LOG] 请求: {}", request);
        request.to_string()
    }
}

src/main.rs:

mod server;
mod router;
mod handlers;
mod middleware;

use server::Server;
use router::Router;
use handlers::api;
use middleware::logger::Logger;

fn main() {
    let mut router = Router::new();
    router.add_route("/health", api::health_check);
    router.add_route("/users", api::get_users);
    
    let logger = Logger::new();
    
    println!("Web服务器启动");
    
    // let server = Server::new("127.0.0.1:8080");
    // server.run();
}

示例3:配置管理系统

src/config/mod.rs:

pub mod database;
pub mod server;
pub mod logging;

use std::fs;
use std::error::Error;

pub struct Config {
    pub database: database::DatabaseConfig,
    pub server: server::ServerConfig,
    pub logging: logging::LogConfig,
}

impl Config {
    pub fn from_file(path: &str) -> Result<Config, Box<dyn Error>> {
        let content = fs::read_to_string(path)?;
        // 这里应该解析配置文件
        Ok(Config {
            database: database::DatabaseConfig::default(),
            server: server::ServerConfig::default(),
            logging: logging::LogConfig::default(),
        })
    }
    
    pub fn default() -> Config {
        Config {
            database: database::DatabaseConfig::default(),
            server: server::ServerConfig::default(),
            logging: logging::LogConfig::default(),
        }
    }
}

src/config/database.rs:

#[derive(Debug)]
pub struct DatabaseConfig {
    pub host: String,
    pub port: u16,
    pub username: String,
    pub password: String,
    pub database: String,
    pub max_connections: u32,
}

impl DatabaseConfig {
    pub fn default() -> DatabaseConfig {
        DatabaseConfig {
            host: String::from("localhost"),
            port: 5432,
            username: String::from("postgres"),
            password: String::from(""),
            database: String::from("myapp"),
            max_connections: 10,
        }
    }
    
    pub fn connection_string(&self) -> String {
        format!(
            "postgresql://{}:{}@{}:{}/{}",
            self.username,
            self.password,
            self.host,
            self.port,
            self.database
        )
    }
}

src/config/server.rs:

#[derive(Debug)]
pub struct ServerConfig {
    pub host: String,
    pub port: u16,
    pub workers: usize,
    pub timeout: u64,
}

impl ServerConfig {
    pub fn default() -> ServerConfig {
        ServerConfig {
            host: String::from("0.0.0.0"),
            port: 8080,
            workers: 4,
            timeout: 30,
        }
    }
    
    pub fn address(&self) -> String {
        format!("{}:{}", self.host, self.port)
    }
}

src/config/logging.rs:

#[derive(Debug)]
pub enum LogLevel {
    Debug,
    Info,
    Warning,
    Error,
}

#[derive(Debug)]
pub struct LogConfig {
    pub level: LogLevel,
    pub file: Option<String>,
    pub console: bool,
}

impl LogConfig {
    pub fn default() -> LogConfig {
        LogConfig {
            level: LogLevel::Info,
            file: None,
            console: true,
        }
    }
}

七、re-exporting

7.1 使用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();
}

fn main() {
    // 外部代码可以这样使用:
    // use restaurant::hosting;
    // hosting::add_to_waitlist();
}

7.2 创建方便的API

// 内部结构
mod shapes {
    pub mod circle {
        pub struct Circle {
            pub radius: f64,
        }
    }
    
    pub mod rectangle {
        pub struct Rectangle {
            pub width: f64,
            pub height: f64,
        }
    }
}

// 重导出,简化外部使用
pub use shapes::circle::Circle;
pub use shapes::rectangle::Rectangle;

fn main() {
    // 用户可以直接使用
    let c = Circle { radius: 5.0 };
    let r = Rectangle { width: 10.0, height: 5.0 };
}

八、包和Crate

8.1 包的结构

my_project/
├── Cargo.toml
├── Cargo.lock
├── src/
│   ├── main.rs      # 二进制crate根
│   ├── lib.rs       # 库crate根
│   └── bin/
│       ├── tool1.rs # 额外的二进制crate
│       └── tool2.rs
├── tests/           # 集成测试
├── benches/         # 基准测试
└── examples/        # 示例

8.2 Cargo.toml配置

[package]
name = "my_project"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = "1.0"

[dev-dependencies]
criterion = "0.5"

[[bin]]
name = "tool1"
path = "src/bin/tool1.rs"

[[bin]]
name = "tool2"
path = "src/bin/tool2.rs"

九、最佳实践

9.1 模块组织原则

  1. 按功能分组:相关的代码放在同一模块
  2. 保持浅层次:避免过深的模块嵌套
  3. 清晰的命名:模块名应该描述其内容
  4. 最小化公开接口:只公开必要的部分

9.2 示例:良好的模块结构

// ✅ 好的结构
mod user {
    pub struct User {
        id: u32,
        name: String,
    }
    
    impl User {
        pub fn new(id: u32, name: String) -> User {
            User { id, name }
        }
        
        pub fn id(&self) -> u32 {
            self.id
        }
        
        // 私有辅助函数
        fn validate_name(name: &str) -> bool {
            !name.is_empty()
        }
    }
}

9.3 避免循环依赖

// ❌ 不好:循环依赖
// mod a {
//     use super::b::B;
// }
// mod b {
//     use super::a::A;
// }

// ✅ 好:提取共享接口
mod common {
    pub trait Processor {}
}

mod a {
    use super::common::Processor;
}

mod b {
    use super::common::Processor;
}

十、练习题

练习1:图书馆系统

创建一个模块化的图书馆管理系统:

// src/lib.rs
pub mod book;
pub mod member;
pub mod library;

// src/book.rs
#[derive(Debug, Clone)]
pub struct Book {
    pub isbn: String,
    pub title: String,
    pub author: String,
    pub available: bool,
}

impl Book {
    pub fn new(isbn: String, title: String, author: String) -> Book {
        Book {
            isbn,
            title,
            author,
            available: true,
        }
    }
}

// src/member.rs
#[derive(Debug)]
pub struct Member {
    pub id: u32,
    pub name: String,
    pub borrowed_books: Vec<String>,
}

impl Member {
    pub fn new(id: u32, name: String) -> Member {
        Member {
            id,
            name,
            borrowed_books: Vec::new(),
        }
    }
}

// src/library.rs
use crate::book::Book;
use crate::member::Member;

pub struct Library {
    books: Vec<Book>,
    members: Vec<Member>,
}

impl Library {
    pub fn new() -> Library {
        Library {
            books: Vec::new(),
            members: Vec::new(),
        }
    }
    
    pub fn add_book(&mut self, book: Book) {
        self.books.push(book);
    }
    
    pub fn add_member(&mut self, member: Member) {
        self.members.push(member);
    }
}

练习2:工具函数库

创建一个包含多个工具模块的库:

// src/lib.rs
pub mod string_utils;
pub mod math_utils;
pub mod file_utils;

// 重导出常用函数
pub use string_utils::capitalize;
pub use math_utils::fibonacci;

总结

本文学习了:

✅ 模块的定义和使用
✅ 路径和可见性控制
✅ use关键字简化路径
✅ 将模块分割到文件
✅ 包和crate的组织
✅ 最佳实践

核心要点

  1. 使用mod定义模块
  2. pub控制可见性
  3. use简化路径
  4. 模块可以分割到多个文件
  5. 合理组织代码结构
Logo

开放原子旋武开源社区(简称“旋武社区”)是由开放原子开源基金会孵化及运营的技术社区,致力于在中国推广和发展Rust编程语言生态,推动Rust在操作系统、终端设备、安全技术、基础软件等关键领域的产业落地,构建安全、可靠、高效的软件基础设施。

更多推荐