learning-rust/part6/包和模块练习题.md

28 KiB
Raw Blame History

Rust Package、Crate、Module 练习题

建议先手动写出每道题的答案,再运行代码验证。部分题目涉及多文件组织,请实际创建对应目录和文件来练习。

目录


一、概念辨析题

判断以下说法是否正确,并简要说明原因。

题目 1-1

一个 Package 只能包含一个 Crate。

题目 1-2

main.rslib.rs 分别对应二进制 Crate 和库 Crate 的根文件。

题目 1-3

在模块树中,父模块默认可以访问子模块中的私有项。

题目 1-4

使用 use 关键字将模块或项引入作用域后,其子模块也会自动被引入。

题目 1-5

cargo new my_project 默认会创建一个二进制 Crate。

题目 1-6

同一个文件中可以定义多个 mod 模块。

题目 1-7

pub use 的作用完全等同于 use,只是书写风格不同。

题目 1-8

在一个 Package 中,src/bin/ 目录下的每个 .rs 文件都会被编译为一个独立的二进制 Crate。


二、填空题:补充代码

补全下列代码使其能通过编译并达到期望输出。

题目 2-1定义模块

// 填空:定义一个名为 network 的模块,内部包含一个名为 connect 的函数
________ network {
    fn connect() {
        println!("已连接");
    }
}

fn main() {
    // 填空:调用 network 模块中的 connect 函数
    network::________();
}
// 期望输出:已连接

题目 2-2pub 可见性

mod garden {
    // 填空:使 plant 函数能够被模块外部访问
    ________ fn plant() -> &'static str {
        "玫瑰花"
    }

    fn water() {
        println!("浇水...");
    }
}

fn main() {
    let flower = garden::plant();
    println!("种了{}", flower);

    // 下面这行取消注释会怎样?
    // garden::water();
}
// 期望输出:种了玫瑰花

题目 2-3use 关键字

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {
            println!("已加入等候列表");
        }

        pub fn seat_at_table() {
            println!("已入座");
        }
    }
}

fn main() {
    // 填空:使用 use 将 hosting 模块引入当前作用域
    ________ front_of_house::hosting;

    hosting::add_to_waitlist();
    hosting::seat_at_table();
}
// 期望输出:
// 已加入等候列表
// 已入座

题目 2-4super 关键字

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

mod back_of_house {
    fn cook() {
        println!("烹饪中...");
    }

    // 填空:使用 super 调用父模块中的 serve_order 函数
    pub fn deliver() {
        cook();
        ________::serve_order();
    }
}

fn main() {
    // 填空:正确调用 deliver 函数
    ________();
}
// 期望输出:
// 烹饪中...
// 上菜!

题目 2-5as 别名

use std::fmt::Result as FmtResult;
// 填空:将 std::io::Result 以别名 IoResult 引入
use std::io::Result ________ ________;

fn main() {
    // 直接使用别名调用 Ok 构造函数
    let _r1: FmtResult = Ok(());
    let _r2: IoResult<()> = Ok(());
    println!("两种 Result 都能正常使用!");
}

题目 2-6嵌套路径导入

// 下面的导入使用了 3 行 use 语句
// use std::cmp::Ordering;
// use std::io;
// use std::io::Write;

// 填空:替换为一行嵌套路径的 use 语句
use std::________;
use std::________::{self, Write};

fn main() {
    println!("模块导入成功!");
}

题目 2-7pub use 重导出

mod inner {
    pub fn secret() -> &'static str {
        "内部机密数据"
    }
}

// 填空:将 inner::secret 重导出为公开接口,使外部可以通过 crate::config 访问
________ inner::secret as config;

fn main() {
    // 填空:通过新的路径名调用
    println!("{}", ________());
}
// 期望输出:内部机密数据

题目 2-8模块拆分到文件

假设 src/main.rs 中有以下代码,需要将 garden 模块拆分到单独的文件中。

// ===== src/main.rs =====
mod garden;

fn main() {
    garden::grow();
}
// 期望输出:植物正在生长...

// ===== 填空:写出 src/garden.rs 的内容 =====
________ fn grow() {
    println!("植物正在生长...");
}

题目 2-9模块拆分到目录

假设有以下模块结构,需要将 restaurant 模块拆分到 src/restaurant/ 目录中。

// ===== src/main.rs =====
mod restaurant;

fn main() {
    restaurant::front::serve();
}
// 期望输出:上菜中...

// ===== 填空:写出需要的文件及其内容 =====
// 文件 1: src/restaurant/________
// 文件 2: src/restaurant/________

// restaurant/________ 内容:
pub mod front;

// restaurant/________ 内容:
pub fn serve() {
    println!("上菜中...");
}

三、找出并修复错误

以下每段代码都有编译错误,请指出错误并写出修正后的代码。

题目 3-1

mod house {
    fn open_door() {
        println!("门开了");
    }
}

fn main() {
    house::open_door();
}

题目 3-2

mod front {
    pub fn greet() {
        println!("欢迎光临!");
    }
}

fn main() {
    use front;
    greet();
}

题目 3-3

mod math {
    pub mod operations {
        pub fn add(a: i32, b: i32) -> i32 {
            a + b
        }
    }
}

fn main() {
    use math::operations;
    println!("{}", add(3, 5));
}

题目 3-4

mod a {
    pub mod b {
        pub fn hello() {
            println!("hello from b");
        }
    }
}

fn main() {
    use a::b;
    // 想要同时使用模块 b 和 cc 不存在)
    use a::c;
    b::hello();
}

题目 3-5

mod outer {
    mod inner {
        pub fn secret_data() -> &'static str {
            "机密"
        }
    }

    pub fn reveal() {
        inner::secret_data();
    }
}

fn main() {
    outer::reveal();
    // 下面这行是否可以?
    // outer::inner::secret_data();
}

题目 3-6

mod food {
    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 = food::Breakfast::summer("黑麦面包");
    meal.toast = String::from("全麦面包");
    println!("我要{}吐司", meal.toast);

    // 下面这行取消注释会怎样?
    // meal.seasonal_fruit = String::from("蓝莓");
}

题目 3-7

// ===== Cargo.toml =====
// [package]
// name = "my_project"
// version = "0.1.0"
// edition = "2021"

// ===== src/main.rs =====
use rand::Rng;

fn main() {
    let mut rng = rand::thread_rng();
    let n: i32 = rng.gen_range(1..100);
    println!("随机数: {}", n);
}
// 运行 cargo build 时报错can't find crate for `rand`

题目 3-8

use std::collections::HashMap;
use std::collections::HashSet;

fn main() {
    let mut map: HashMap<&str, i32> = HashMap::new();
    map.insert("one", 1);

    let mut set: HashSet<i32> = HashSet::new();
    set.insert(1);

    println!("HashMap: {:?}", map);
    println!("HashSet: {:?}", set);
}
// 虽然编译通过,但如何将两个 use 语句合并为一行?

四、实战题:组织代码

题目 4-1餐厅管理系统

请按以下要求组织一个餐厅管理系统的模块结构,并实现全部代码。

模块结构要求:

restaurant (库 Crate)
├── front_of_house/
│   ├── mod.rs          → pub mod hosting; pub mod serving;
│   ├── hosting.rs      → pub fn add_to_waitlist() { ... }
│   │                      pub fn seat_at_table() { ... }
│   └── serving.rs      → pub fn take_order() { ... }
│                          pub fn serve_order() { ... }
│                          pub fn take_payment() { ... }
├── back_of_house.rs    → pub fn cook() { ... }
│                          fn wash_dishes() { ... }  ← 私有函数
│                          pub fn clean_up() { ... } 调用 wash_dishes()
├── menu.rs             → pub enum Dish { ... }
│                          impl Dish { fn price(&self) -> f64 }
└── lib.rs              → pub mod front_of_house;
                           pub mod back_of_house;
                           pub mod menu;
                           pub fn eat_at_restaurant() { 演示调用各模块 }

要求:

  1. menu.rs 中定义 Dish 枚举,包含以下变体和价格:

    • Steak → 168.0
    • Salad → 38.0
    • Pasta → 58.0
    • Water → 0.0(免费)
  2. Dish 实现 price(&self) -> f64 方法

  3. 实现 eat_at_restaurant() 函数,演示完整的用餐流程:

    • 加入等候列表
    • 入座
    • 点菜(点一个牛排和一份沙拉)
    • 上菜
    • 结账(打印总价)
    • 清理
  4. 创建对应的目录结构,写出所有文件的完整代码

题目 4-2数学工具库

创建一个名为 math_tools 的库 Crate按以下层级组织代码。

模块结构:

math_tools (库 Crate)
├── lib.rs
├── basic/
│   ├── mod.rs        → pub mod arithmetic; pub mod compare;
│   ├── arithmetic.rs → pub fn add, sub, mul, div
│   └── compare.rs    → pub fn min, max, is_even
├── advanced/
│   ├── mod.rs        → pub mod stats; pub mod geometry;
│   ├── stats.rs      → pub fn mean, median, variance
│   └── geometry.rs   → pub fn circle_area, rect_area, triangle_area
└── utils.rs          → pub fn is_prime(n: u32) -> bool
                        pub fn factorial(n: u32) -> u64

要求:

  1. 实现全部函数
  2. lib.rs 中使用 pub use 将以下函数重导出为库的顶层接口:
    • addmeanis_primecircle_area
  3. lib.rs 中写测试(#[cfg(test)] mod tests)验证以下场景:
    • add(2, 3) == 5
    • mean(&[1.0, 2.0, 3.0, 4.0, 5.0]) == 3.0
    • is_prime(17)is_prime(18)
    • circle_area(1.0) 约等于 π

题目 4-3多二进制 Crate 的 Package

创建一个 Package包含一个库 Crate 和两个二进制 Crate。

结构:

my_app/
├── Cargo.toml         → package name = "my_app"
├── src/
│   ├── lib.rs         → 库 Crate定义 Config 结构体,包含 host, port, debug 字段
│   │                     和 parse_args 函数,解析命令行参数
│   └── bin/
│       ├── server.rs  → 二进制 Crate启动一个模拟服务器打印配置信息
│       └── client.rs  → 二进制 Crate模拟连接到服务器打印连接信息

要求:

  1. 库中定义 Config 结构体:
    pub struct Config {
        pub host: String,
        pub port: u16,
        pub debug: bool,
    }
    
  2. 库中实现 Confignew 关联函数(使用默认值)
  3. server.rs 调用库的 Config::new(),打印 "服务器启动于 {host}:{port}",如果 debug 为 true 再打印调试信息
  4. client.rs 调用库的 Config::new(),打印 "客户端连接到 {host}:{port}"
  5. 创建完整的目录结构,写出所有文件内容

五、综合思考题

题目 5-1

分析以下模块结构。假设当前在 crate::outer::middle::inner 模块中,有哪些方式可以访问 crate::outer::top_level 中的 value 函数?请写出至少三种不同的路径写法。

// lib.rs
pub mod outer {
    pub fn top_level() {}

    pub mod middle {
        pub mod inner {
            pub fn deep() {
                // 在此处调用 outer::top_level()
            }
        }
    }
}

pub mod another {
    pub fn value() {
        println!("another value");
    }
}

题目 5-2

对比以下三种 use 导入方式的优缺点和使用场景:

// 方式 A导入模块本身
use std::collections;
// 使用: collections::HashMap::new()

// 方式 B导入具体类型
use std::collections::HashMap;
// 使用: HashMap::new()

// 方式 C通配符导入
use std::collections::*;
// 使用: HashMap::new(), HashSet::new(), BTreeMap::new()

题目 5-3

Rust 的可见性规则中,pub 的"公开"是相对于模块路径而言的,而不是公开给所有人。请解释以下代码中为什么 outer::inner::secret 虽然标记为 pub,但在 main 中仍然无法直接访问。

mod outer {
    mod inner {  // inner 模块本身是私有的
        pub fn secret() {
            println!("秘密");
        }
    }

    pub fn reveal() {
        inner::secret();  // 可以,因为 outer 是 inner 的父模块
    }
}

fn main() {
    outer::reveal();     // 可以
    // outer::inner::secret();  // 取消注释会怎样?
}

题目 5-4

阅读以下代码,回答:

mod parent {
    pub fn parent_func() {
        println!("父函数");
    }

    pub mod child {
        pub fn child_func() {
            // 如何在 child 模块中调用 parent_func()
        }
    }
}

mod sibling {
    pub fn sibling_func() {
        // 如何在此处调用 parent::parent_func()
    }
}
  1. child 模块中调用 parent_func() 应该使用什么路径?(写出具体写法)
  2. sibling 模块中调用 parent_func() 应该使用什么路径?
  3. supercrate 分别指代什么?各自适用于什么场景?

题目 5-5

pub modpub use 有什么区别?以下两种写法分别适用于什么场景?

// 写法 A嵌套模块 + 重导出
mod internal {
    pub fn helper() {}
}
pub use internal::helper;

// 写法 B先定义公开模块再导入使用
pub mod api {
    pub fn helper() {}
}

参考答案

请独立完成再查看答案。

点击展开答案

一、概念辨析题

1-1 错误。一个 Package 最多包含一个库 Crate但可以包含任意多个二进制 Crate。例如 src/main.rs 是一个二进制 Cratesrc/bin/ 下的每个文件也是独立的二进制 Crate同时还可以有一个 src/lib.rs 作为库 Crate。

1-2 正确。src/main.rs 是二进制 Crate 的根文件crate rootsrc/lib.rs 是库 Crate 的根文件。Cargo 会将根文件传递给 rustc 来构建 crate。

1-3 错误。在模块树中,子模块可以访问父模块中的所有项(包括私有项),但反过来不成立——父模块不能访问子模块中的私有项。要将子模块的项暴露给外部,必须用 pub 标记。

1-4 错误。use 只引入指定的路径,不会自动引入子模块。例如 use std::collections; 只引入 collections 模块本身,collections 下的 HashMap 等仍需要通过 collections::HashMap 访问。

1-5 正确。cargo new my_project 创建的模板包含 src/main.rs,是一个二进制 Crate。cargo new my_lib --lib 创建的是库 Cratesrc/lib.rs)。

1-6 正确。同一个文件中可以定义任意多个 mod 块,每个块成为一个独立的模块。

1-7 错误。use 将路径引入当前作用域供自己使用,外部代码无法感知。pub use重导出re-exporting,不仅引入当前作用域,还将其作为当前模块的公开 API 暴露给外部使用者。

1-8 正确。src/bin/ 目录下的每个 .rs 文件会被 Cargo 自动编译为独立的二进制 Crate名称等于文件名不含扩展名

二、填空题

2-1

mod network {
    fn connect() {
        println!("已连接");
    }
}

fn main() {
    network::connect();
}

2-2

mod garden {
    pub fn plant() -> &'static str {
        "玫瑰花"
    }
    fn water() { println!("浇水..."); }
}

fn main() {
    let flower = garden::plant();
    println!("种了{}", flower);
    // garden::water(); 取消注释会报错water 是私有函数
}

2-3

use front_of_house::hosting;

2-4

pub fn deliver() {
    cook();
    super::serve_order();
}

fn main() {
    back_of_house::deliver();
}

2-5

use std::io::Result as IoResult;

2-6

use std::{cmp::Ordering, io::{self, Write}};

2-7

pub use inner::secret as config;

fn main() {
    println!("{}", config());
}

2-8

文件: src/garden.rs
pub fn grow() {
    println!("植物正在生长...");
}

2-9

文件 1: src/restaurant/mod.rs
内容: pub mod front;

文件 2: src/restaurant/front.rs
内容: pub fn serve() { println!("上菜中..."); }

三、修复错误

3-1open_door 是私有函数,模块外部无法访问。修复:加 pub

mod house {
    pub fn open_door() { println!("门开了"); }
}

3-2use front; 只引入了模块,没有引入函数。函数名本身不在作用域中。修复:

use front::greet;
// 或 use front; 后调用 front::greet();

3-3use math::operations; 引入的是模块,add 需要通过 operations::add() 调用。修复:

use math::operations::add;
println!("{}", add(3, 5));

3-4:模块 a::c 不存在。如果 c 不需要,直接删除该行。如果想引入两个模块,应等模块存在。此题故意制造了一个引入不存在的模块的错误。

3-5:主函数中 outer::inner::secret_data() 不能使用。虽然 secret_data 标记了 pub,但 inner 模块本身是私有的(没有 pub mod),外部无法通过私有模块访问其内部的任何项。修复:将 mod inner 改为 pub mod inner

3-6:取消注释会报错,因为 seasonal_fruit 字段是私有的,即便结构体实例是 mut,也不能修改私有字段。只有直接拥有该结构体的模块才能访问私有字段。但如果改为:

meal.seasonal_fruit = String::from("蓝莓");

编译错误:seasonal_fruit is private。外模块只能修改 pub 字段。

3-7Cargo.toml 中未添加 rand 依赖。修复:在 [dependencies] 下添加:

[dependencies]
rand = "0.8"

然后运行 cargo buildCargo 会自动下载并编译 rand

3-8:合并为:

use std::collections::{HashMap, HashSet};

四、实战题

4-1 餐厅管理系统:

文件结构:

src/
├── lib.rs
├── front_of_house/
│   ├── mod.rs
│   ├── hosting.rs
│   └── serving.rs
├── back_of_house.rs
└── menu.rs
// src/lib.rs
pub mod front_of_house;
pub mod back_of_house;
pub mod menu;

use menu::Dish;

pub fn eat_at_restaurant() {
    front_of_house::hosting::add_to_waitlist();
    front_of_house::hosting::seat_at_table();

    let order = vec![Dish::Steak, Dish::Salad];
    front_of_house::serving::take_order(order.clone());
    front_of_house::serving::serve_order();

    let total: f64 = order.iter().map(|d| d.price()).sum();
    println!("总消费: {:.2} 元", total);

    front_of_house::serving::take_payment();
    back_of_house::clean_up();
}
// src/front_of_house/mod.rs
pub mod hosting;
pub mod serving;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {
    println!("已加入等候列表");
}

pub fn seat_at_table() {
    println!("已入座");
}
// src/front_of_house/serving.rs
use crate::menu::Dish;

pub fn take_order(dishes: Vec<Dish>) {
    print!("点菜: ");
    for (i, dish) in dishes.iter().enumerate() {
        if i > 0 { print!(", "); }
        print!("{:?}", dish);
    }
    println!();
}

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

pub fn take_payment() {
    println!("结账完成!");
}
// src/back_of_house.rs
pub fn cook() {
    println!("烹饪中...");
}

fn wash_dishes() {
    println!("洗碗中...");
}

pub fn clean_up() {
    cook();
    println!("厨房已清理");
    wash_dishes();
}
// src/menu.rs
#[derive(Debug, Clone)]
pub enum Dish {
    Steak,
    Salad,
    Pasta,
    Water,
}

impl Dish {
    pub fn price(&self) -> f64 {
        match self {
            Dish::Steak => 168.0,
            Dish::Salad => 38.0,
            Dish::Pasta => 58.0,
            Dish::Water => 0.0,
        }
    }
}

4-2 数学工具库:

文件结构:

src/
├── lib.rs
├── basic/
│   ├── mod.rs
│   ├── arithmetic.rs
│   └── compare.rs
├── advanced/
│   ├── mod.rs
│   ├── stats.rs
│   └── geometry.rs
└── utils.rs
// src/lib.rs
pub mod basic;
pub mod advanced;
pub mod utils;

pub use basic::arithmetic::add;
pub use advanced::stats::mean;
pub use advanced::geometry::circle_area;
pub use utils::is_prime;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }

    #[test]
    fn test_mean() {
        assert_eq!(mean(&[1.0, 2.0, 3.0, 4.0, 5.0]), 3.0);
    }

    #[test]
    fn test_is_prime() {
        assert!(is_prime(17));
        assert!(!is_prime(18));
    }

    #[test]
    fn test_circle_area() {
        let area = circle_area(1.0);
        assert!((area - std::f64::consts::PI).abs() < 1e-10);
    }
}
// src/basic/mod.rs
pub mod arithmetic;
pub mod compare;
// src/basic/arithmetic.rs
pub fn add(a: i32, b: i32) -> i32 { a + b }
pub fn sub(a: i32, b: i32) -> i32 { a - b }
pub fn mul(a: i32, b: i32) -> i32 { a * b }
pub fn div(a: i32, b: i32) -> Option<i32> {
    if b == 0 { None } else { Some(a / b) }
}
// src/basic/compare.rs
pub fn min(a: i32, b: i32) -> i32 { if a < b { a } else { b } }
pub fn max(a: i32, b: i32) -> i32 { if a > b { a } else { b } }
pub fn is_even(n: i32) -> bool { n % 2 == 0 }
// src/advanced/mod.rs
pub mod stats;
pub mod geometry;
// src/advanced/stats.rs
pub fn mean(data: &[f64]) -> f64 {
    let sum: f64 = data.iter().sum();
    sum / data.len() as f64
}

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

pub fn variance(data: &[f64]) -> f64 {
    let m = mean(data);
    data.iter().map(|x| (x - m).powi(2)).sum::<f64>() / data.len() as f64
}
// src/advanced/geometry.rs
use std::f64::consts::PI;

pub fn circle_area(radius: f64) -> f64 { PI * radius * radius }
pub fn rect_area(width: f64, height: f64) -> f64 { width * height }
pub fn triangle_area(base: f64, height: f64) -> f64 { base * height / 2.0 }
// src/utils.rs
pub fn is_prime(n: u32) -> bool {
    if n < 2 { return false; }
    for i in 2..=((n as f64).sqrt() as u32) {
        if n % i == 0 { return false; }
    }
    true
}

pub fn factorial(n: u32) -> u64 {
    (1..=n).fold(1, |acc, x| acc * x as u64)
}

4-3 多二进制 Crate Package

文件结构:

my_app/
├── Cargo.toml
└── src/
    ├── lib.rs
    └── bin/
        ├── server.rs
        └── client.rs
# Cargo.toml
[package]
name = "my_app"
version = "0.1.0"
edition = "2021"
// src/lib.rs
pub struct Config {
    pub host: String,
    pub port: u16,
    pub debug: bool,
}

impl Config {
    pub fn new() -> Config {
        Config {
            host: String::from("127.0.0.1"),
            port: 8080,
            debug: false,
        }
    }
}
// src/bin/server.rs
use my_app::Config;

fn main() {
    let config = Config::new();
    println!("服务器启动于 {}:{}", config.host, config.port);
    if config.debug {
        println!("[调试模式] 详细日志已启用");
    }
}
// src/bin/client.rs
use my_app::Config;

fn main() {
    let config = Config::new();
    println!("客户端连接到 {}:{}", config.host, config.port);
}

运行方式:

cargo run --bin server   # 启动服务器
cargo run --bin client   # 启动客户端

五、综合思考题

5-1:在 inner::deep() 中访问 outer::top_level() 的三种方式:

pub fn deep() {
    // 方式 1super回到父模块 middle再回到 outer
    super::super::top_level();

    // 方式 2crate 绝对路径
    crate::outer::top_level();

    // 方式 3self + 多级 super
    // self::super::super::top_level();  // 等效于 super::super
}

若要访问 another::value()

super::super::super::another::value();  // 不够优雅
crate::another::value();                // 推荐:使用 crate 根路径

5-2

维度 方式 A导入模块 方式 B导入类型 方式 C通配符
清晰度 中等(知道来自哪个模块) 低(看不出来自哪个模块) 最低(完全不清楚来源)
简洁度 低(写路径长) 高(直接使用类型名)
名称冲突风险 中等(可能与其他同名类型冲突) 高(极易引入未知冲突)
适用场景 需要明确模块来源时;模块下类型少时 高频使用的具体类型 测试模块、prelude 模式;生产代码不推荐

最佳实践:

  • 常规代码:使用方式 A模块导入调用时写 collections::HashMap
  • 高频类型:使用方式 B
  • 避免方式 C除测试模块和 prelude 设计)

5-3

虽然 secret 标记为 pub,但 inner 模块本身是私有的mod inner 没有 pub。Rust 的可见性是分层级的:父模块定义子模块的可见范围。因为 inner 模块对外不可见,即使它内部的项全是 pub,外部也无法访问——路径在 inner 这一层就已经堵死了。

类比:一个没有挂牌的办公楼(私有模块),即使一楼大堂对所有访客开放(内部函数是 pub),路人也找不到入口。

5-4

  1. child 中调用 parent_func()

    super::parent_func();
    
  2. sibling 中调用 parent_func()

    crate::parent::parent_func();
    
  3. supercrate 的区别:

    • super:指向当前模块的父模块,适用于在同级模块间或访问父模块内容时使用。
    • crate:指向crate 根目录(即 lib.rsmain.rs 顶层),适用于跨模块树访问时使用绝对路径。
    • 场景:父子关系简单、层级少时用 super;跨多层级或需要稳定路径时用 crate

5-5

写法 A内部模块 + 重导出

mod internal {
    pub fn helper() {}
}
pub use internal::helper;
  • internal 模块是私有的,外部不知道它的存在
  • 通过 pub usehelper 提升到外层模块的公开 API 中
  • 适用于:内部组织代码但对外隐藏实现细节。外部调用者只看到 helper,不知道它来自 internal 模块。后续可以自由重构 internal 而不会破坏 API。

写法 B公开模块

pub mod api {
    pub fn helper() {}
}
  • api 模块整体暴露在 API 中
  • 外部使用时需要 api::helper(),模块结构成为 API 的一部分
  • 适用于:模块结构本身就是设计意图的一部分,希望用户理解模块层级。但将模块布局暴露后,修改结构可能破坏下游代码。

总结:

  • 写法 Apub use 重导出):灵活、可卸载、隐藏内部结构;库设计常用模式
  • 写法 Bpub mod):简单直接、但模块结构成为契约的一部分,重构成本高