Rust从入门到精通之入门篇:10.包和模块

包和模块

在本章中,我们将学习 Rust 的包和模块系统,它们是组织和重用代码的重要工具。随着项目规模的增长,良好的代码组织变得越来越重要,Rust 提供了一套强大的机制来管理代码结构。

包和 Crate

Crate

Crate 是 Rust 中最高级别的代码组织单位。一个 crate 可以是一个二进制项目或一个库。

  • 二进制 crate:可以编译为可执行文件,必须有一个 main 函数
  • 库 crate:提供功能给其他 crate 使用,没有 main 函数

包(package)是一个或多个 crate 的集合,提供一组功能。一个包包含一个 Cargo.toml 文件,描述如何构建这些 crate。

包的规则:

  • 一个包最多可以包含一个库 crate
  • 可以包含任意数量的二进制 crate
  • 必须至少包含一个 crate(库或二进制)

Cargo 基础

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

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.rssrc/lib.rs

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

路径

路径用于在模块树中找到项。路径可以是:

  • 绝对路径:从 crate 根开始,使用 crate 名或字面值 crate
  • 相对路径:从当前模块开始,使用 selfsuper 或当前模块的标识符
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

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 关键字

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 关键字

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() {}

工作区(Workspaces)

工作区是一组共享 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" }

发布 crate 到 crates.io

准备发布

  1. 添加文档注释
  2. 设置元数据:
[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):

  • 主版本号:不兼容的 API 更改
  • 次版本号:向后兼容的功能添加
  • 修订号:向后兼容的错误修复

示例程序

让我们创建一个简单的库,展示 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("程序执行完毕");
}

练习题

  1. 创建一个名为 geometry 的库 crate,包含计算不同形状(圆形、矩形、三角形)面积和周长的函数。使用适当的模块结构组织代码。

  2. 扩展上面的库,添加一个 utils 模块,包含用于验证输入值的函数(例如,检查边长是否为正数)。

  3. 创建一个二进制 crate,使用你的 geometry 库计算并打印不同形状的面积和周长。

  4. 创建一个工作区,包含两个相关的包:一个提供基本数学运算,另一个使用这些运算实现更复杂的功能。

  5. 修改 geometry 库,使其可以发布到 crates.io(添加适当的文档、描述和许可证信息)。

总结

在本章中,我们学习了:

  • Rust 的包和 crate 系统
  • 使用 Cargo 管理项目
  • 创建和组织模块
  • 控制项的可见性(公有和私有)
  • 使用路径引用模块中的项
  • 使用 use 关键字简化路径
  • 将模块拆分到多个文件
  • 创建和管理工作区
  • 准备和发布 crate

良好的代码组织是构建可维护软件的关键。Rust 的模块系统提供了强大的工具,帮助你组织代码,控制可见性,并创建清晰的 API。随着你的 Rust 项目变得更加复杂,这些技能将变得越来越重要。

你可能感兴趣的:(Rust从入门到精通系列,rust,开发语言,后端)