参考:https://kaisery.github.io/trpl-zh-cn/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html

  • (package): Cargo 的一个功能,它允许你构建、测试和分享 crate。
  • crate :一个模块的树形结构,它形成了库 (library) 或二进制项目 (binary)。
  • 模块 (module) 和 use : 允许你控制作用域和路径的私有性。
  • (item):指放入模块内的事物,比如 结构体、枚举、函数、特性、方法、常量、模块,默认自身和内部都是私有的 (pravite)。
  • 路径 (path):一个命名例如结构体、函数或模块等项的方式

Rust 提供了将包分成多个 crate,将 crate 分成模块,以及通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。你可以通过使用 use 语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加 pub 关键字使其定义变为公有。

package

package 是提供一系列功能的一个或者多个 crate。一个 package 包含:

  • 一个 Cargo.toml 文件,阐述如何去构建这些 crate
  • 至多包含一个 library crate
  • 可以包含任意 “多个” binary crate
  • 至少包含一个 crate,无论是 library 还是 binary

    crate

    crate:Rust中的一个编译单元。 crate 可以编译为二进制文件或库文件。默认情况下,rustc some_file.rs 将从 some_file crate 生成一个二进制文件。可以使用 --crate-type=lib 参数来编译成 rlib 文件。 crate file:每当调用 rustc some_file.rs 成功编译时时, some_file.rs 都将其视为 crate file。 如果 some_file.rs 包含 mod 声明,则在对它运行编译器之前,会将 mod 块的内容插入到 crate file 中。换句话说,模块不会单独编译,只有 crate 才能编译。

crate 是一个二进制项目 (binary) 或者库 (library)。
crate root 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块。
src/main.rs 是 binary crate 的 crate root,而 binary crate 与 package 同名,即主目录的文件名
src/lib.rs 是 library crate 的 crate root,而 library crate 与 package 同名,即主目录的文件名
crate root 将由 Cargo 传递给 rustc 来实际构建库或者二进制项目
crate 与 scope:

  • crate 将一个作用域内的相关功能分组到一起,使得该功能可以很方便地在多个项目之间共享。举一个例子, rand crate 提供了生成随机数的功能。通过将 rand crate 加入到我们项目的作用域中,我们就可以在自己的项目中使用该功能。rand crate 提供的所有功能都可以通过该 crate 的名字:rand 进行访问。
  • 将一个 crate 的功能保持在其自身的作用域中,可以知晓一些特定的功能是在我们的 crate 中定义的还是在 rand crate 中定义的,这可以防止潜在的名称冲突。例如,rand crate 提供了一个名为 Rng 的特性(trait)。我们还可以在我们自己的 crate 中定义一个名为 Rngstruct。因为一个 crate 的功能是在自身的作用域进行命名的,当我们将 rand 作为一个依赖,编译器不会混淆 Rng 这个名字的指向。在我们的 crate 中,它指向的是我们自己定义的 struct Rng。我们可以通过 rand::Rng 这一方式来访问 rand crate 中的 Rng 特性(trait)。

cargo new my-project 命令会创建一个 my-project 文件主目录:

  1. 目录下面有 Cargo.toml 文件和 src/main.rs 文件,意味着它只含有一个名为 my-project 的 binary crate。
  2. 如果一个 package 同时含有 src/main.rssrc/lib.rs,则它有两个 crate:一个 binary 和一个 library,且名字都与包相同。
  3. 通过将文件放在 src/bin 目录下,一个包可以拥有多个 binary crate:
    • 使用 cargo build --binssrc/main.rs 和每个src/bin下的文件都会被编译成一个独立的 binary crate
    • 使用 cargo run --bin 单个二进制crate名称:编译和运行单个 binary crate
  4. 为什么只能有一个 library crate?你使用的他人的 crate 公开的公有代码实际上是他人 crate 的 lib crate 公开出来的内容,根据上面第 2 条说明,lib 的 crate 名只有一个,也就是他人在 crate.io 看到的 crate 名。
  5. 为什么可以有多个 binary crate?因为 binary 不会提供内部的代码接口给其他人,二进制文件只是执行文件而已;多个 binary crate 意味着 src/bin 下每个 rs 文件名对应各自同名的二进制文件。
  6. 为什么 library 和 binary crate 与 package 同名,它们不会因为名称发生冲突吗?并不会。记住
    • main.rs 的目的是生成二进制文件,此 binary 的名称就是 package 名称;
    • lib.rs 的目的是公开代码接口给外部调用,这里的外部有两类:让同 package 的 main.rs 调用(或者说 把 main.rs 的代码拆分进 lib.rs),或者发布到 crate.io 之类的平台上,让其他人调用,调用时引入的名称就是 package 名称。
  7. main.rs 一定要拆分代码进 lib.rs 吗?它们必须共存吗?不是一定共存的,它们可以单独存在。但是一般把 main.rs 看作功能代码实现,把 lib.rs 看作核心代码实现,使用 main.rs + lib.rs 的结构是清晰和通用的,尤其使用 src/bin 多个二进制 crate 需要公有代码的时候。main.rs 可以借助 mod 拆分文件的方式把代码模块化,让那些代码完全只给 binary crate 服务。让lib 和 binary 共存的其他理由:
    • 不满足于单元测试,想让 binary crate 集成测试。因为 lib crate 可以做单元和集成测试,而 binary 只能单元测试。
    • 产生二进制文件的同时,考虑把一些二进制文件具备或者不具备的功能代码公布,让其他人使用。
    • 上面这种情况的另一面:写一个公开功能、让其他人调用的 crate,同时考虑用这些功能产生二进制文件。
  8. 如何在 src/main.rs 或者 src/bin 下的 rs 文件 调用 src/lib.rs (或者说 library crate)公开的代码?
    • 默认已经被引入,使用 crate::公开的功能 即可,或者使用use crate名称::功能 把一部分功能引入作用域。
    • 注意由于变量名不允许 -,引入时需要把 - 写成成 _。即在 my-project/src/main.rs 中使用 use my_project; 即可调用 lib.rs 公有代码。
    • 有些老的项目代码会看到类似 extern crate my_project; use my_project; 方式引入 crate,这是 2018 版本之前的写法,目前只需要使用 use,或者直接从 crate 名称开始使用。
  9. 不管是 lib 还是 bin crate,下面 module 的规则都适用于这两种 crate。

    module

    mod 定义模块

  • 将一个 crate 中的代码进行分组,以提高可读性与重用性,就像用文件夹给文件分组一样:将相关的定义分组到一起,并(通过名称)指出他们为什么相关。程序员可以通过使用这段代码,更加容易地找到他们想要的定义,因为他们可以基于分组来对代码进行导航,而不需要阅读所有的定义。程序员向这段代码中添加一个新的功能时,他们也会知道代码应该放置在何处,可以保持程序的组织性。
  • 控制内部代码的私有性,即控制哪些是可以被外部代码使用的 (public),哪些是作为一个内部实现的内容,不能被外部代码使用 (private)。模块相当于划定一条私有性边界privacy boundary ):这条界线不允许外部代码了解、调用和依赖被封装的实现细节,所以,如果你希望创建一个私有函数或结构体,你可以将其放入模块。
  • 结构体、枚举、函数、特性、方法、常量、模块 (这些被称为模块的 items ) ,默认自身和内部都是私有的 (pravite),比如模块自己和里面的内容默认私有。

    1. #![allow(unused)]
    2. fn main() {
    3. // 前台是招待顾客的地方
    4. mod front_of_house {
    5. // 店主可以为顾客安排座位
    6. mod hosting {
    7. fn add_to_waitlist() {}
    8. fn seat_at_table() {}
    9. }
    10. // 服务员接受顾客下单和付款
    11. mod serving {
    12. fn take_order() {}
    13. fn server_order() {}
    14. fn take_payment() {}
    15. }
    16. }
    17. }
    1. module tree (模块树)
    2. crate
    3. └── front_of_house
    4. ├── hosting
    5. ├── add_to_waitlist
    6. └── seat_at_table
    7. └── serving
    8. ├── take_order
    9. ├── serve_order
    10. └── take_payment

    引用模块的 item

    使用路径的方式在模块树中找到一个项 ( “item” ) 的位置,路径有两种形式:

  • 绝对路径 absolute path :从 “crate 根” 开始,以 crate 名或者字面值(关键字) crate 开头,类似于在 shell 中使用 / 从文件系统根开始。

  • 相对路径 relative path :从当前模块开始,以 selfsuper 或当前模块的标识符开头。

绝对路径和相对路径都后跟一个或多个由双冒号(::)分割的标识符。

  1. // `front_of_house` 这个模块和里面的内容并不会在 crate 层面公开出去
  2. // 它只对当前 rs 文件下?平级 (sibiling) 的 item 公开,比如 `eat_at_restaurant`
  3. mod front_of_house {
  4. pub mod hosting {
  5. pub fn add_to_waitlist() {}
  6. }
  7. }
  8. // pub 表示将这个 item 公开出去可以让其他人调用
  9. pub fn eat_at_restaurant() {
  10. // Absolute path
  11. crate::front_of_house::hosting::add_to_waitlist();
  12. // Relative path
  13. front_of_house::hosting::add_to_waitlist();
  14. }
  15. fn main() {}

选择哪种路径,取决于你是更倾向于将项的定义代码与使用该项的代码分开来移动 -> 绝对路径,还是一起移动 -> 相对路径。举一个例子,如果我们要将 front_of_house 模块和 eat_at_restaurant 函数一起移动到一个名为 customer_experience 的模块中,我们需要更新 add_to_waitlist 的绝对路径,但是相对路径还是可用的。然而,如果我们要将 eat_at_restaurant 函数单独移到一个名为 dining 的模块中,还是可以使用原本的绝对路径来调用 add_to_waitlist,但是相对路径必须要更新。我们更倾向于使用绝对路径,因为把代码定义和项调用各自独立地移动是更常见的

super 引入上级模块

使用 super 开头来构建从父模块开始的相对路径,类似于文件系统中以 .. 开头的语法。

  1. fn serve_order() {}
  2. mod back_of_house {
  3. fn fix_incorrect_order() {
  4. cook_order();
  5. super::serve_order();
  6. }
  7. fn cook_order() {}
  8. }
  9. fn main() {}

比如我们认为 back_of_house 模块和 serve_order 函数之间可能具有某种关联关系,并且,如果我们要重新组织这个 crate 的模块树,需要一起移动它们。因此,我们使用 super,这样一来,如果这些代码被移动到了其他模块,我们只需要更新很少的代码。

self 引入当前模块

结合 use 使用:

  1. use std::fmt::{self, Formatter} // 表示同时引入 `std::fmt` 和 `std::fmt::Formatter`

也可以在模块内使用,引入当前模块的子模块:

  1. fn function() {
  2. println!("called `function()`");
  3. }
  4. mod cool {
  5. pub fn function() {
  6. println!("called `cool::function()`");
  7. }
  8. }
  9. mod my {
  10. fn function() {
  11. println!("called `my::function()`");
  12. }
  13. mod cool {
  14. pub fn function() {
  15. println!("called `my::cool::function()`");
  16. }
  17. }
  18. pub fn indirect_call() {
  19. // Let's access all the functions named `function` from this scope!
  20. print!("called `my::indirect_call()`, that\n> ");
  21. // The `self` keyword refers to the current module scope - in this case `my`.
  22. // Calling `self::function()` and calling `function()` directly both give
  23. // the same result, because they refer to the same function.
  24. self::function();
  25. function();
  26. // We can also use `self` to access another module inside `my`:
  27. self::cool::function();
  28. // The `super` keyword refers to the parent scope (outside the `my` module).
  29. super::function();
  30. // This will bind to the `cool::function` in the *crate* scope.
  31. // In this case the crate scope is the outermost scope.
  32. {
  33. use crate::cool::function as root_function;
  34. root_function();
  35. }
  36. }
  37. }
  38. fn main() {
  39. my::indirect_call();
  40. }

把模块拆分进文件

随着代码逐渐增加,模块的内容越来越多,事情变得复杂起来,我们可以把模块内部拆分进文件。

  1. // src/lib.rs
  2. mod front_of_house {
  3. pub mod hosting {
  4. pub fn add_to_waitlist() {}
  5. }
  6. }
  7. pub use crate::front_of_house::hosting;
  8. pub fn eat_at_restaurant() {
  9. hosting::add_to_waitlist();
  10. hosting::add_to_waitlist();
  11. hosting::add_to_waitlist();
  12. }

从最外层的 front_of_house 模块开始,把内容逐渐拆解开来:

  1. 把模块名保留,并添加上 ;,再把里面的内容原封不动地移入同名文件。

    1. // src/lib.rs
    2. mod front_of_house; // 声明与 front_of_house 同名的 rs 文件
    3. pub use crate::front_of_house::hosting;
    4. pub fn eat_at_restaurant() {
    5. hosting::add_to_waitlist();
    6. hosting::add_to_waitlist();
    7. hosting::add_to_waitlist();
    8. }
    1. // src/front_of_house.rs
    2. pub mod hosting {
    3. pub fn add_to_waitlist() {}
    4. }

    这样就把最外层模块从 lib.rs 中拆分出来了

  2. front_of_househosting 是上下的层级关系,如果 hosting 模块变成了 hosting.rs ,那么 front_of_house 只能变成同名文件夹,而不再是 rs 文件。所以现在把 src/front_of_house.rs 文件保留,内部 hosting 模块部分变成模块声明 pub mod hosting;;创建目录和文件 src/front_of_house/hosting.rssrc/lib.rs内容不变,因为仍然需要声明front_of_house模块,把 hosting 模块的内容移入src/front_of_house/hosting.rs 同名文件。

最终的文件树:

  1. .
  2. ├── Cargo.toml
  3. ├── src
  4. ├── front_of_house
  5. └── hosting.rs
  6. ├── front_of_house.rs
  7. └── lib.rs

相应的代码内容如下:

  1. // src/lib.rs
  2. mod front_of_house; // 声明与 front_of_house 同名的 rs 文件
  3. pub use crate::front_of_house::hosting;
  4. pub fn eat_at_restaurant() {
  5. hosting::add_to_waitlist();
  6. hosting::add_to_waitlist();
  7. hosting::add_to_waitlist();
  8. }
  1. // src/front_of_house.rs
  2. pub mod hosting;
  1. // src/front_of_house/hosting.rs
  2. pub fn add_to_waitlist() {}

可使用 cargo test 的 lib crate demo: multi_lib.tar.gz

  1. .
  2. ├── Cargo.lock
  3. ├── Cargo.toml
  4. └── src
  5. ├── front_of_house
  6. └── hosting.rs
  7. ├── front_of_house.rs
  8. └── lib.rs
  1. // src/lib.rs
  2. mod front_of_house; // 声明与 front_of_house 同名的 rs 文件
  3. pub use crate::front_of_house::hosting;
  4. pub fn eat_at_restaurant() -> u32 {
  5. hosting::add_to_waitlist()
  6. }
  7. #[cfg(test)]
  8. mod tests {
  9. #[test]
  10. fn it_works() {
  11. assert_eq!(super::eat_at_restaurant(), 0);
  12. }
  13. }
  1. // src/front_of_house.rs
  2. pub mod hosting;
  1. // src/front_of_house/hosting.rs
  2. pub fn add_to_waitlist() -> u32 {
  3. println!("from add_to_waitlist");
  4. 0
  5. }

还可以使用 mod.rs 文件做到上面这个例子的效果:

  1. .
  2. ├── Cargo.lock
  3. ├── Cargo.toml
  4. └── src
  5. ├── front_of_house
  6. └── mod.rs
  7. └── lib.rs
  1. // src/lib.rs
  2. pub use crate::front_of_house::hosting;
  3. mod front_of_house; // 声明 front_of_house 是个 mod
  4. // 而实际 front_of_house 是一个文件夹,同时里面必须有 mod.rs
  5. // 从而就不需要 src/front_of_house.rs 文件了
  6. #[cfg(test)]
  7. mod tests {
  8. use super::*; // 为了简便没写 eat_at_restaurant 这个函数
  9. #[test]
  10. fn it_works() {
  11. assert_eq!(hosting::add_to_waitlist(), 0);
  12. }
  13. }
  1. // src/front_of_house/mod.rs
  2. // 如果不想把 hosting 模块拆分成文件,直接在这里定义好 hosting 模块和里面的内容
  3. pub mod hosting {
  4. pub fn add_to_waitlist() ->u32 {
  5. 0
  6. }
  7. }

或者这样的模式:

  1. .
  2. ├── Cargo.lock
  3. ├── Cargo.toml
  4. └── src
  5. ├── front_of_house
  6. ├── hosting.rs
  7. └── mod.rs
  8. └── lib.rs
  1. // src/lib.rs
  2. pub use crate::front_of_house::hosting;
  3. mod front_of_house; // 声明 front_of_house 是个 mod
  4. // 而实际 front_of_house 是一个文件夹,同时里面必须有 mod.rs
  5. // 从而就不需要 src/front_of_house.rs 文件了
  6. #[cfg(test)]
  7. mod tests {
  8. use super::*;
  9. #[test]
  10. fn it_works() {
  11. assert_eq!(hosting::add_to_waitlist(), 0);
  12. }
  13. }
  1. // src/front_of_house/mod.rs
  2. // 预先定义好 hosting 模块
  3. pub mod hosting;
  1. // src/front_of_house/hosting.rs
  2. // 直接把 mod hosting 里面的代码写入到文件
  3. pub fn add_to_waitlist() -> u32 {
  4. 0
  5. }

其他例子:https://doc.rust-lang.org/rust-by-example/mod/split.html
总而言之,模块拆分进文件的思路:

  1. 文件名.rs 的 文件名就是模块名,直接在文件里面填充属于这个模块的内容。当然文件名就是模块名的前提 是在它的父级模块用 mod ...; 声明好:
    • front_of_house 的父模块是 crate root,也就是 src/lib.rs 这个文件里面有 front_of_house 的声明
    • hosting 的父模块是 front_of_house,要么是在 front_of_house.rs 被声明,要么是在 src/front_of_house/mod.rs 被声明
  2. front_of_house.rs 等价于 src/front_of_house/mod.rs。
    • 前者更直观,因为可以直接辨识 front_of_house 是一个模块,而且同名文件和文件夹里面就是模块的内容;后者更传统,因为需要点进文件夹才能确定这是一个 mod,很多旧项目是这种模式,因为前者是 2018 版本的添加内容
    • 而且后者这种方式还有一个好处,在进行集成测试的时候,tests/front_of_house/mod.rs 不会被 cargo test 测试,所以它可以存放多个集成测试文件都会用到的共享内容。”demo 见集成测试” 。
    • 使用前者方式是做不到的,因为每个集成测试文件会被当作其各自的 crate 来对待,这更有助于创建单独的作用域,这种单独的作用域能提供更类似与最终使用者使用 crate 的环境 —— tests/front_of_house.rs 会被单独测试,即便这个文件并没有包含任何测试函数。

      pub 暴露路径

      通过使用 pub 关键字来创建公共项:
  • 对于 crate root 下的 item:使该 item 公开出去可以让其他人利用 crate 来调用。
  • 对于模块:使子模块的内部部分暴露给上级模块
  • 对于结构体:遵循常规,公开结构体时,其内容(字段、方法)全部是私有的,但是可以继续单独使用 pub 来对个内容公开。
  • 对于枚举体:公开枚举体时,其成员默认变成公有的。如果枚举成员不是公有的,那么枚举会显得用处不大。

    pub mod

    “公开模块。”
    比如下面的hosting模块因为pub关键字而暴露给了上级模块front_of_house,从而上级模块front_of_house和与此平级的 item 都可使用。
    1. mod front_of_house {
    2. pub mod hosting {
    3. fn add_to_waitlist() {}
    4. }
    5. }
    但是模块全部内部默认是私有的,它们并不能看到 hosting 下的 add_to_waitlist 。如果需要把模块的内容公开出去,必须把这部分内容添加上 pub 关键字:#to-comfirm#
    1. // `front_of_house` 这个模块和里面的内容并不会在 crate 层面公开出去
    2. // 它只对当前 rs 文件下?平级 (sibiling) 的 item 公开,比如 `eat_at_restaurant`
    3. mod front_of_house {
    4. pub mod hosting {
    5. pub fn add_to_waitlist() {}
    6. }
    7. }
    8. // pub 表示将这个 item 公开出去可以让其他人调用
    9. pub fn eat_at_restaurant() {
    10. // Absolute path
    11. crate::front_of_house::hosting::add_to_waitlist();
    12. // Relative path
    13. front_of_house::hosting::add_to_waitlist();
    14. }
    15. fn main() {}

    pub struct

    “只公开结构体名称”,其方法、字段默认不公开(需要公开则加 pub)。
    1. mod back_of_house {
    2. pub struct Breakfast {
    3. // 这里公开了一个字段
    4. pub toast: String,
    5. seasonal_fruit: String,
    6. }
    7. impl Breakfast {
    8. // 这里公开了方法
    9. pub fn summer(toast: &str) -> Breakfast {
    10. Breakfast {
    11. toast: String::from(toast),
    12. seasonal_fruit: String::from("peaches"),
    13. }
    14. }
    15. }
    16. }
    17. pub fn eat_at_restaurant() {
    18. // Order a breakfast in the summer with Rye toast
    19. let mut meal = back_of_house::Breakfast::summer("Rye");
    20. // Change our mind about what bread we'd like
    21. meal.toast = String::from("Wheat");
    22. println!("I'd like {} toast please", meal.toast);
    23. // The next line won't compile if we uncomment it; we're not allowed
    24. // to see or modify the seasonal fruit that comes with the meal
    25. // meal.seasonal_fruit = String::from("blueberries");
    26. }

    pub enum

    “内部所有成员公开。”
    1. mod back_of_house {
    2. pub enum Appetizer {
    3. Soup,
    4. Salad,
    5. }
    6. }
    7. pub fn eat_at_restaurant() {
    8. let order1 = back_of_house::Appetizer::Soup;
    9. let order2 = back_of_house::Appetizer::Salad;
    10. }

    pub use 重导出名称

    将 item 引入作用域并同时使其可供其他代码引入自己的作用域。re-export
    1. // src/lib.rs
    2. mod front_of_house {
    3. pub mod hosting {
    4. pub fn add_to_waitlist() {}
    5. }
    6. }
    7. pub use crate::front_of_house::hosting;
    8. pub fn eat_at_restaurant() {
    9. hosting::add_to_waitlist();
    10. hosting::add_to_waitlist();
    11. hosting::add_to_waitlist();
    12. }
    pub use 做了两件事:
  1. usehosting 作用域引入进来,可以无需通过它的上级模块去访问调用,因此作用域范围内都可使用,这是 use 的作用
  2. pub 直接公开了 crate::front_of_house::hosting,外部(调用此 crate 时、或者在此 crate 内的其他 rs 文件)可以直接使用 hostinghosting 内部公开的 item,从公有性的角度看,eat_at_restauranthosting 一样可以使用,而 front_of_house 是私有的,外部不能使用。

当你的代码的内部结构与调用你的代码的程序员的思考领域不同时,重导出会很有用。例如,在这个餐馆的比喻中,经营餐馆的人会想到“前台”和“后台”。但顾客在光顾一家餐馆时,可能不会以这些术语来考虑餐馆的各个部分。使用 pub use,我们可以使用一种结构编写代码,却将不同的结构形式暴露出来。这样做使我们的库井井有条,方便开发这个库的程序员和调用这个库的程序员之间组织起来。

pub(路径) 在父级/祖级模块导出

parent module:父级模块,指当前所处模块的上一级
ancestor module:祖级模块,指上级模块以上的模块

  1. pub(crate) itemA:导出到当前 crate,当前 crate 的所有 items 都可以根据路径使用 itemA
  2. pub(in crate::ancestor) itemA:导出到 ancestor 模块,可以根据 crate::ancestor::itemA 使用 itemA
  3. pub(super) itemA:导出到上级模块,路径到上级模块时,无需往下即可使用 itemA
  4. pub(self) itemA:等价于 `itemA`,需要根据当前模块是否 pub 才能判断 itemA 是否 pub
  5. 可以根据路径使用的意思是:
  6. 1. 平级模块之间直接使用,无需 pub 关键字
  7. 2. 非平级模块,目标模块必须是 pub 才能被使用内部 items,否则即使目标模块内部 items pub,也无法被使用

pub 完整例子

例子来源:https://doc.rust-lang.org/rust-by-example/mod/struct_visibility.html

  1. // A module named `my_mod`
  2. mod my_mod {
  3. // Items in modules default to private visibility.
  4. fn private_function() {
  5. println!("called `my_mod::private_function()`");
  6. }
  7. // Use the `pub` modifier to override default visibility.
  8. pub fn function() {
  9. println!("called `my_mod::function()`");
  10. }
  11. // Items can access other items in the same module,
  12. // even when private.
  13. pub fn indirect_access() {
  14. print!("called `my_mod::indirect_access()`, that\n> ");
  15. private_function();
  16. }
  17. // Modules can also be nested
  18. pub mod nested {
  19. pub fn function() {
  20. println!("called `my_mod::nested::function()`");
  21. }
  22. #[allow(dead_code)]
  23. fn private_function() {
  24. println!("called `my_mod::nested::private_function()`");
  25. }
  26. // Functions declared using `pub(in path)` syntax are only visible
  27. // within the given path. `path` must be a parent or ancestor module
  28. pub(in crate::my_mod) fn public_function_in_my_mod() {
  29. print!("called `my_mod::nested::public_function_in_my_mod()`, that\n> ");
  30. public_function_in_nested();
  31. }
  32. // Functions declared using `pub(self)` syntax are only visible within
  33. // the current module, which is the same as leaving them private
  34. pub(self) fn public_function_in_nested() {
  35. println!("called `my_mod::nested::public_function_in_nested()`");
  36. }
  37. // Functions declared using `pub(super)` syntax are only visible within
  38. // the parent module
  39. pub(super) fn public_function_in_super_mod() {
  40. println!("called `my_mod::nested::public_function_in_super_mod()`");
  41. }
  42. }
  43. pub fn call_public_function_in_my_mod() {
  44. print!("called `my_mod::call_public_function_in_my_mod()`, that\n> ");
  45. nested::public_function_in_my_mod();
  46. print!("> ");
  47. nested::public_function_in_super_mod();
  48. }
  49. // pub(crate) makes functions visible only within the current crate
  50. pub(crate) fn public_function_in_crate() {
  51. println!("called `my_mod::public_function_in_crate()`");
  52. }
  53. // Nested modules follow the same rules for visibility
  54. mod private_nested {
  55. #[allow(dead_code)]
  56. pub fn function() {
  57. println!("called `my_mod::private_nested::function()`");
  58. }
  59. // Private parent items will still restrict the visibility of a child item,
  60. // even if it is declared as visible within a bigger scope.
  61. #[allow(dead_code)]
  62. pub(crate) fn restricted_function() {
  63. println!("called `my_mod::private_nested::restricted_function()`");
  64. }
  65. }
  66. }
  67. fn function() {
  68. println!("called `function()`");
  69. }
  70. fn main() {
  71. // Modules allow disambiguation between items that have the same name.
  72. function();
  73. my_mod::function();
  74. // Public items, including those inside nested modules, can be
  75. // accessed from outside the parent module.
  76. my_mod::indirect_access();
  77. my_mod::nested::function();
  78. my_mod::call_public_function_in_my_mod();
  79. // pub(crate) items can be called from anywhere in the same crate
  80. my_mod::public_function_in_crate();
  81. // pub(in path) items can only be called from within the module specified
  82. // Error! function `public_function_in_my_mod` is private
  83. // my_mod::nested::public_function_in_my_mod();
  84. // TODO ^ Try uncommenting this line
  85. // Private items of a module cannot be directly accessed, even if
  86. // nested in a public module:
  87. // Error! `private_function` is private
  88. // my_mod::private_function();
  89. // TODO ^ Try uncommenting this line
  90. // Error! `private_function` is private
  91. // my_mod::nested::private_function();
  92. // TODO ^ Try uncommenting this line
  93. // Error! `private_nested` is a private module
  94. // my_mod::private_nested::function();
  95. // TODO ^ Try uncommenting this line
  96. // Error! `private_nested` is a private module
  97. // my_mod::private_nested::restricted_function();
  98. // TODO ^ Try uncommenting this line
  99. }

打印结果:

  1. called `function()`
  2. called `my_mod::function()`
  3. called `my_mod::indirect_access()`, that
  4. > called `my_mod::private_function()`
  5. called `my_mod::nested::function()`
  6. called `my_mod::call_public_function_in_my_mod()`, that
  7. > called `my_mod::nested::public_function_in_my_mod()`, that
  8. > called `my_mod::nested::public_function_in_nested()`
  9. > called `my_mod::nested::public_function_in_super_mod()`
  10. called `my_mod::public_function_in_crate()`

use 将名称引入作用域

use 自己的 crate

在作用域中增加 use 和路径类似于在文件系统中创建软连接(符号连接,symbolic link)。

  1. // src/lib.rs
  2. mod front_of_house {
  3. pub mod hosting {
  4. pub fn add_to_waitlist() {}
  5. }
  6. }
  7. use crate::front_of_house::hosting;
  8. // 上面的 use 和下面的 use 是等价的
  9. // 因为 src/lib.rs 是 crate root,上面的语句类似于绝对引用
  10. // 又因为需要引入的作用域就在当前文件夹,下面的语句类似于相对引用
  11. // use front_of_house::hosting;
  12. pub fn eat_at_restaurant() {
  13. // 引入函数时,指定到父模块,再调用
  14. hosting::add_to_waitlist();
  15. hosting::add_to_waitlist();
  16. hosting::add_to_waitlist();
  17. }
  18. fn main() {}

为什么不使用 use crate::front_of_house::hosting::add_to_waitlist; 来直接把函数引入作用域?因为 add_to_waitlist 函数是外部定义的(这个例子中 add_to_waitlisteat_at_restaurant 并不能直接相互使用,所以是外部的),为了表明这种外部关系,指定父模块来调用函数的方式就很清楚。这是一种惯例。
另一种惯例是 use 引入结构体、枚举和其他 item 时,习惯是指定它们的完整路径,然后直接使用它们

  1. // 引入结构体、枚举和其他 item 时,指定完整路径
  2. use std::collections::HashMap;
  3. fn main() {
  4. // 直接调用
  5. let mut map = HashMap::new();
  6. map.insert(1, 2);
  7. }

当使用来自不同模块但是相同名称的 item 时,由于 Rust 不允许同一个作用域出现两个相同名称的 item:

  • 要么指定到父级模块:

    1. // 除非模块的子级有相同名称的 item
    2. use std::fmt;
    3. use std::io;
    4. fn function1() -> fmt::Result {
    5. // --snip--
    6. }
    7. fn function2() -> io::Result<()> {
    8. // --snip--
    9. }
  • 要么使用 as 关键字提供新的名称:

    1. use std::fmt::Result;
    2. use std::io::Result as IoResult;
    3. fn function1() -> Result {
    4. // --snip--
    5. Ok(())
    6. }
    7. fn function2() -> IoResult<()> {
    8. // --snip--
    9. Ok(())
    10. }

    use 外部的 crate

  1. Cargo.toml 中加入外部 crate 的名称,比如

    1. [dependencies]
    2. rand = "0.8.3"
  2. 在项目代码中就已经加载了这个 外部 crate 命名空间,因此对 crate 层面 pub 的 item,直接使用 外部 crate 名::item (绝对路径),比如 rand::thread_rng()。注意 函数 rand::thread_rng() 是通过 pub use 重定向出来的,完整的路径是 crate::rngs::thread::thread_rng

  3. 将其他 item 引入就要使用 use 了,比如 Rng trait (随机数生成器,提供了 gen_range 方法)未在crate 层面公开出来,所以需要引入作用域:

    1. use rand::Rng;
    2. fn main() {
    3. let secret_number = rand::thread_rng().gen_range(1..101);
    4. }

    注意:标准库(std)对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 Cargo.toml 来引入 std,不过需要通过 use 将标准库中定义的项引入项目包的作用域中来引用它们,比如我们使用的 HashMap

    1. use std::collections::HashMap;

    这是一个以标准库 crate 名 std 开头的绝对路径。

    use 其他的使用方法

  4. 整合引入定义于相同 package 或相同模块的 item:使用 {...} 的嵌套 (nested),可以显著减少所需的独立 use 语句的数量

    1. // 这样写比较啰嗦
    2. use std::cmp::Ordering;
    3. use std::io;
    4. // 这样看起来就清晰很多,把 `::` 看作类似于 `/` 路径的分隔符号
    5. use std::{cmp::Ordering, io};
  5. 可以使用 self 关键字来表示嵌套外层的路径本身:

    1. use std::io;
    2. use std::io::Write;
    3. // 等价于
    4. use std::io::{self, Write};
  6. 通过 * (glob 运算符) 将所有的公有定义引入作用域:

    1. // 将 std::collections 中定义的所有公有项引入当前作用域。
    2. use std::collections::*;

    使用 glob 运算符时请多加小心!Glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。
    glob 运算符经常用于测试模块 tests 中,这时会将所有内容引入作用域;我们将在第十一章 “如何编写测试” 部分讲解。
    glob 运算符有时也用于 prelude 模式;查看 标准库中的文档 了解这个模式的更多细节。