【Rust】包和模块管理,以及作用域等问题——Rust语言基础15 ...

一给  金牌会员 | 2025-3-22 16:28:02 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 948|帖子 948|积分 2844

1. 媒介

经过上一小节无聊又风趣的洗礼相信大家已经提起精神进入下一个内容的学习啦~~
这小节将会了解 Rust 中是以什么样的形式和工具来构造和管理自己的代码。我们都知道当代码量和源文件数目到达一定水平时候,井然有序的构造将变得尤为重要,不然我们的代码在外人眼里看来就是一坨*,不但其他人难以阅读,作为开发者的你回头看去也是一头雾水,悔恨自己写的这是个什么玩意儿。
在 Rust 中有着严格的作用域限制,有如许的一个模块体系(the model system)来管理作用域,此中包罗:


  • (Packages):Cargo 的一个功能,它答应你构建、测试和分享 crate;
  • Crates :一个模块的树形布局,它形成了库或二进制项目;
  • 模块(Modules)和 use:答应你控制作用域和路径的私有性;
  • 路径(path):一个定名比方布局体、函数或模块等项的方式。
2. 包和 Crate

crate 是 Rust 在编译时最小的代码单位。如果你用 rustc 而不是 cargo 来编译一个文件,编译器还是会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译。
crate 有两种形式:二进制项。二进制项可以被编译为可实行步伐,好比一个命令行步伐大概一个 web server。它们必须有一个 main 函数来定义当步伐被实行的时候所需要做的事情。现在我们所创建的 crate 都是二进制项。
并没有 main 函数,它们也不会编译为可实行步伐,库可以提供一些函数或布局体之类的,就如之前我们所使用过的 rand 库就为我们提供了随机数函数。
包(package)是提供一系列功能的一个大概多个 crate。一个包会包含一个 Cargo.toml 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依靠的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行步伐一样的逻辑。
包中可以包含至多一个库 crate(library crate)。包中可以包含恣意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。
让我们来看看创建包的时候会发生什么。首先,我们输入命令 cargo new my-project:
  1. $ cargo new my-project
  2.      Created binary (application) `my-project` package
  3. $ ls my-project
  4. Cargo.toml
  5. src
  6. $ ls my-project/src
  7. main.rs
复制代码
Cargo 给我们创建了什么,Cargo 会给我们的包创建一个 Cargo.toml 文件。检察 Cargo.toml 的内容,会发现并没有提到 src/main.rs,因为 Cargo 默认 src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。
同样实行 cargo new --lib my-library 会有同样的目次布局生成,不同的是这里的 src/main.rs 变成了 src/lib.rs,并且 src/lib.rs 就是 crate 根。crate 根文件将由 Cargo 传递给 rustc 来实际构建库大概二进制项目。
这里只是包含一个 src/main.rs 的包,意味着它只含有一个名为 my-project 的二进制 crate。如果一个包同时含有 src/main.rs 和 src/lib.rs,则它有两个 crate:一个二进制的和一个库的,且名字都与包雷同。通过将文件放在 src/bin 目次下,一个包可以拥有多个二进制 crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。
做个简朴的实验,首先实行 cargo new multiple_bin
  1. $ cargo new multiple_bin
  2. Creating binary (application) `multiple_bin` package
  3. note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
  4. $ cd multiple_bin
  5. $ tree
  6. .
  7. ├── Cargo.lock
  8. ├── Cargo.toml
  9. └── src
  10.     └── main.rs
复制代码
进入 src,创建 bin 目次,并在此中创建多个 .rs 文件:
  1. $ cd src
  2. $ mkdir bin
  3. $ cp ../main.rs bin01.rs
  4. $ cp ../main.rs bin02.rs
  5. $ cd ../../
复制代码
编译该项目:
  1. $ cargo build
  2.    Compiling multiple_bin v0.1.0 (/home/im01/Miracle/rust/multiple_bin)
  3.     Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
  4. $ tree
  5. .
  6. ├── Cargo.lock
  7. ├── Cargo.toml
  8. ├── src
  9. │   ├── bin
  10. │   │   ├── bin01.rs
  11. │   │   └── bin02.rs
  12. │   └── main.rs
  13. └── target
  14.     ├── CACHEDIR.TAG
  15.     └── debug
  16.         ├── bin01
  17.         ├── bin01.d
  18.         ├── bin02
  19.         ├── bin02.d
  20.         ├── build
  21.         ├── deps
  22.         │   ├── bin01-54a8a90b566c47ce
  23.         │   ├── bin01-54a8a90b566c47ce.d
  24.         │   ├── bin02-057c31f4258a913d
  25.         │   ├── bin02-057c31f4258a913d.d
  26.         │   ├── multiple_bin-79155e437d2fa379
  27.         │   └── multiple_bin-79155e437d2fa379.d
  28.         ├── examples
  29.         ├── incremental
  30.         ......
  31.         ├── multiple_bin
  32.         └── multiple_bin.d
  33. 15 directories, 48 files
复制代码
[注]:这里为了简便仅展示了部门重要内容。
从 target 目次可以看出这里不但会为 main.rs 生成了与根同名的 multiple_bin 二进制的 crate,还会为在 bin 目次下的两个文件生成对应文件名的二进制 crate。
3. 定义模块以及模块之间的关系

这里将会涉及到几个重要的关键字:


  • use:将路径引入作用域;
  • pub:使对应项变为公有性质;
  • as:为同名函数起别名。
在此之前先对几个概念做以解释:


  • rust 项目是从 crate 根节点开始检索代码:这很好理解,对于一个二进制 crate 的根就是 src/main.rs,而库则是 src/lib.rs,就类似在 C/C++ 中总是以 main 函数开始;
  • 声明模块:在 crate 根文件中用 mod 关键字可以声明一个模块,如:
  1.         mod xiaomi_car;        // 中国最大的保时捷&法拉利元素融合高性能新能源汽车集团
复制代码
这便是声明白一个 xiaomi_car 模块,而当 mod xiaomi_car 后是一个大括号时,如许的方式成为内联,如:
  1.         mod xiaomi_car{
  2.                 fn sale() {}        // 销售部销售小米汽车,金牌销售员:雷将军
  3.         }
复制代码


  • 声明子模块:在不是 main.rs 中定义的模块被称为子模块;
  • 公有和私有:一个模块里的代码默认对其夫模块私有。为一个模块加上 pub 关键字即使用 pub mod 来声明模块,则表示将该模块公有化;
看完这些名词解释相信大家在大脑中还是一团浆糊,那么接下来将通过一点小实验渐渐让我们梳理清晰如今正在学习的到底是什么。
当下火出天际的汽车行业的雷小军的保时米为例来介绍,保时米集团有如许的部门专门用来营销宣传以及销售汽车被称为销售部(Sales Department),还有专门用来生产制造的制造部(Manufacturing Department)。
好了,如今让我们来创建一个小米汽车的库,起名为 xiaomi_car。
  1. $ cargo new --lib xiaomi_car
  2.     Creating library `xiaomi_car` package
  3. note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
复制代码
编辑 src/lib.rs 文件添加如下内容:
  1. mod sales_department {
  2.         // 车辆售前服务
  3.     mod pre_sales_service {
  4.             // 车型咨询
  5.         fn model_consultation() {}
  6.                 // 制定购车方案
  7.         fn purchase_plan_options() {}
  8.     }
  9.    
  10.     // 车辆售后服务
  11.     mod after_sales_service {
  12.             // 车辆保养服务
  13.         fn vehicle_maintenance_services() {}
  14.                 // 维修服务
  15.         fn repair_services() {}
  16.     }
  17. }
复制代码
这里我们用关键字 mod 定义了一个名为 sales_department 的模块,接着花括号内为该模块的主体部门。在模块内仍然可以指定其它模块,正如这里的 pre_sales_service 和 after_sales_service 模块是属于 sales_department 的子模块,相应的 sales_department 是他们的父模块。而模块内还可以定义一些其它的各种类型,如布局体、枚举、常量、函数等。
通过以模块的方式将相干的代码定义再一起,如许会更有构造性的管理步伐,以 src/main.rs 或 src/lib.rs 作为 crate 根,构成了一整个模块树(module tree)。
上面的代码就展示如下如许布局的设备树。
  1. crate
  2. └── sales_department
  3.      ├── pre_sales_service
  4.      │   ├── model_consultation
  5.      │   └── purchase_plan_options
  6.      └── after_sales_service
  7.          ├── vehicle_maintenance_services
  8.          └── repair_services
复制代码
在 rust 里仍然是借用家庭关系来形貌模块之间的关系,在同一个模块中定义的子模块互为兄弟模块(siblings module),而包含着子模块的模块称为他们的父模块(parent module)。注意,整个模块树都植根于名为 crate 的隐式模块下。如许一来我们就可以更加清晰的设计和构造我们的代码。
4. 作用域问题

4.1. 作用域问题初现

上面我们了解到模块之间的布局可以被抽象成为树状布局,那么不同层之间的模块是否可以或许相互调用呢?(既然已经这么问了,那么一定是不可以咯~)总而言是,先试试看吧。
  1. // 保时米的销售部门
  2. mod sales_department {
  3.         // 车辆售前服务
  4.     mod pre_sales_service {
  5.             // 车型咨询
  6.         fn model_consultation() {}
  7.                 // 制定购车方案
  8.         fn purchase_plan_options() {}
  9.         // 添加到生产订单
  10.                 fn add_to_production_order() {}
  11.     }
  12.    
  13.     // 车辆售后服务
  14.     mod after_sales_service {
  15.             // 车辆保养服务
  16.         fn vehicle_maintenance_services() {}
  17.                 // 维修服务
  18.         fn repair_services() {}
  19.     }
  20. }
  21. // 保时米的生产制造部门
  22. mod manufacturing_department {
  23.         // 生产计划
  24.     mod production_planning {
  25.             // 制定生产计划
  26.         fn specify_production_plan() {
  27.                         // 添加到生产订单
  28.             add_to_production_order();
  29.         }
  30.     }
  31.         // 总装车间
  32.     mod final_assembly_workshop {
  33.     }
  34. }
复制代码
让我们就如许编译看下能不能通过:
  1. im01@Ubuntu:xiaomi_car$ cargo build
  2.    Compiling xiaomi_car v0.1.0 (/home/im01/Miracle/rust/xiaomi_car)
  3. error[E0425]: cannot find function `add_to_production_order` in this scope
  4.   --> src/lib.rs:23:13
  5.    |
  6. 23 |             add_to_production_order();
  7.    |             ^^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
  8.    |
  9. note: function `crate::sales_department::pre_sales_service::add_to_production_order` exists but is inaccessible
  10.   --> src/lib.rs:7:9
  11.    |
  12. 7  |         fn add_to_production_order() {}
  13.    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not accessible
  14. For more information about this error, try `rustc --explain E0425`.
  15. error: could not compile `xiaomi_car` (lib) due to 1 previous error
复制代码
哦~哦吼吼~,不出意外的出意外了,从错误信息中可以看到,没有在当前范围内找到 add_to_production_order 这个函数,再看到下面的提示告诉我们说,有一个同名的函数存在,但是不可以访问。注意一下,这里有两点问题:


  • 第一:我们函数调用的方式不对,因为调用了一个不存在的函数;
  • 第二:即便我们去调用这个存在的函数,同样存在不可访问的问题。
4.2. 办理问题一

根据提示我们可以办理第一个问题,修改上面代码:
  1. mod sales_department {
  2.     mod pre_sales_service {
  3.             //-------------snip----------------
  4.         fn add_to_production_order() {}
  5.                 //-------------snip----------------
  6.     }
  7. }
  8. mod manufacturing_department {
  9.     mod production_planning {
  10.         fn specify_production_plan() {
  11.             //add_to_production_order();
  12.                         // 绝对路径
  13.             crate::sales_department::pre_sales_service::add_to_production_order();
  14.         }
  15.     }
  16.     mod final_assembly_workshop {
  17.     }
  18. }
  19. pub fn sales_announcement() {
  20.         // 相对路径
  21.     sales_department::pre_sales_service::add_to_production_order();
  22. }
复制代码
[注]:此处为了突出重要内容,省略了部门无关代码。这里为了说明相对路径问题额外添加 sales_announcement 函数。
这里引入两个概念:


  • 绝对路径: 从 crate 开始,即从根开始按照树状布局索引出来的,每一层之间用 :: 隔开;
  • 相对路径: 从当前位置出发,即从同一层的模块位置出发索引,同样每层之间用 :: 隔开。
好了,这里我们编译一下,看看会出现什么问题:
  1. im01@Ubuntu:xiaomi_car$ cargo build
  2.    Compiling xiaomi_car v0.1.0 (/home/im01/Miracle/rust/xiaomi_car)
  3. error[E0603]: module `pre_sales_service` is private
  4.   --> src/lib.rs:25:38
  5.    |
  6. 25 |             crate::sales_department::pre_sales_service::add_to_production_order();
  7.    |                                      ^^^^^^^^^^^^^^^^^  ----------------------- function `add_to_production_order` is not publicly re-exported
  8.    |                                      |
  9.    |                                      private module
  10.    |
  11. note: the module `pre_sales_service` is defined here
  12.   --> src/lib.rs:2:5
  13.    |
  14. 2  |     mod pre_sales_service {
  15.    |     ^^^^^^^^^^^^^^^^^^^^^
  16. error[E0603]: module `pre_sales_service` is private
  17.   --> src/lib.rs:37:23
  18.    |
  19. 37 |     sales_department::pre_sales_service::add_to_production_order();
  20.    |                       ^^^^^^^^^^^^^^^^^  ----------------------- function `add_to_production_order` is not publicly re-exported
  21.    |                       |
  22.    |                       private module
  23.    |
  24. note: the module `pre_sales_service` is defined here
  25.   --> src/lib.rs:2:5
  26.    |
  27. 2  |     mod pre_sales_service {
  28.    |     ^^^^^^^^^^^^^^^^^^^^^
  29. For more information about this error, try `rustc --explain E0603`.
  30. error: could not compile `xiaomi_car` (lib) due to 2 previous errors
复制代码
错误信息中告诉我们 pre_sales_service 是一个私有模块,被 sales_department 模块私有,因此外部无法访问(这里所指的外部是 pre_sales_service 同层之外)。
4.3. 办理问题二

此时的整个模块树看起来是如许的。
  1. crate
  2. ├── sales_department
  3. │   ├── pre_sales_service
  4. │   │   ├── model_consultation
  5. │   │   ├── purchase_plan_options
  6. │   │   └── add_to_production_order
  7. │   └── after_sales_service
  8. │       ├── vehicle_maintenance_services
  9. │       └── repair_services
  10. └── manufacturing_department
  11.          ├── production_planning
  12.          │   └── specify_production_plan
  13.          └── final_assembly_workshop
复制代码
要想使得外部也可以访问,这里就需要使用到关键字 pub。
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         fn model_consultation() {}
  4.         fn purchase_plan_options() {}
  5.         fn add_to_production_order() {}
  6.     }
  7.     mod after_sales_service {
  8.         fn vehicle_maintenance_services() {}
  9.         fn repair_services() {}
  10.     }
  11. }
  12. //---------------snip---------------------------
复制代码
那么只在 pre_sales_service 前加上 pub 是否就可以了呢?编译试试看:
  1. im01@Ubuntu:xiaomi_car$ cargo build
  2.    Compiling xiaomi_car v0.1.0 (/home/im01/Miracle/rust/xiaomi_car)
  3. error[E0603]: function `add_to_production_order` is private
  4.   --> src/lib.rs:25:57
  5.    |
  6. 25 |             crate::sales_department::pre_sales_service::add_to_production_order();
  7.    |                                                         ^^^^^^^^^^^^^^^^^^^^^^^ private function
  8.    |
  9. note: the function `add_to_production_order` is defined here
  10.   --> src/lib.rs:7:9
  11.    |
  12. 7  |         fn add_to_production_order() {}
  13.    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  14. error[E0603]: function `add_to_production_order` is private
  15.   --> src/lib.rs:37:42
  16.    |
  17. 37 |     sales_department::pre_sales_service::add_to_production_order();
  18.    |                                          ^^^^^^^^^^^^^^^^^^^^^^^ private function
  19.    |
  20. note: the function `add_to_production_order` is defined here
  21.   --> src/lib.rs:7:9
  22.    |
  23. 7  |         fn add_to_production_order() {}
  24.    |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  25. For more information about this error, try `rustc --explain E0603`.
  26. error: could not compile `xiaomi_car` (lib) due to 2 previous errors
复制代码
正如之前所说,外部无法访问指的外部是同层之外。因此 add_to_production_order 函数前也需要加上 pub。
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         fn model_consultation() {}
  4.         fn purchase_plan_options() {}
  5.         pub fn add_to_production_order() {}
  6.     }
  7.     mod after_sales_service {
  8.         fn vehicle_maintenance_services() {}
  9.         fn repair_services() {}
  10.     }
  11. }
  12. //---------------snip---------------------------
复制代码
如许一来编译就没问题了。到这里相信大家已经粗略的感受到了在 rust 中作用域的概念。
4.4. super 关键字

这里换一种方式去调用 add_to_production_order。
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         fn model_consultation() {}
  4.         fn purchase_plan_options() {}
  5.         pub fn add_to_production_order() {}
  6.     }
  7.     mod after_sales_service {
  8.         fn vehicle_maintenance_services() {}
  9.         fn repair_services() {}
  10.     }
  11. }
  12. mod manufacturing_department {
  13.     mod production_planning {
  14.         fn specify_production_plan() {
  15.             //add_to_production_order();
  16.             crate::sales_department::pre_sales_service::add_to_production_order();
  17.                         // 等同于上一行
  18.             super::super::sales_department::pre_sales_service::add_to_production_order();
  19.         }
  20.     }
  21.     mod final_assembly_workshop {
  22.     }
  23. }
  24. pub fn sales_announcement() {
  25.     sales_department::pre_sales_service::add_to_production_order();
  26. }
复制代码
此处便用到了 super 关键字,其作用想必各位从形势中也窥窃出一二来,没错 super 关键字的作用类似与 Linux 文件体系中的 .. 语法——到上一级目次,而相应的这里则是到上一级模块层。
为什么 rust 会多此一举的设计如许的关键字,缘故原由很简朴,当全文都在使用绝对路径,如许没错,但会显得代码冗长。而全文又使用相对路径,则会导致逻辑看起来混乱,难以阅读,一旦代码做过改动尤其是移动之后,将会带来相应的错误,而定位起来也较为不便,那么有了 super 的引入之后,我们很清晰的知道模块之间的关系,当代码整体移动时,也不必担心路径不对而需要修改调用路径。就是如许。
4.5. 将路径引入作用域

上面两个问题的办理方法虽然有用,但是仍然让代码显得冗长,这里将会介绍一个关键字 use 直接将需要的路径引入当前作用域,可以极大的简化代码的长度。
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         fn model_consultation() {}
  4.         fn purchase_plan_options() {}
  5.         pub fn add_to_production_order() {}
  6.     }
  7. }
  8. mod manufacturing_department {
  9.     mod production_planning {
  10.                 // 使用 use 关键字
  11.         use crate::sales_department::pre_sales_service;
  12.         fn specify_production_plan() {
  13.             //add_to_production_order();
  14.             pre_sales_service::add_to_production_order();
  15.         }
  16.     }
  17. }
复制代码
在当前作用域中增长 use 和路径类似于在文件体系中创建软毗连(符号毗连,symbolic link)。需要注意的是,use 只能对当前作用域范围内有用,若上面代码改为如下如许,则会无法使用到 use 引入进来的路径:
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         fn model_consultation() {}
  4.         fn purchase_plan_options() {}
  5.         pub fn add_to_production_order() {}
  6.     }
  7. }
  8. // 使用 use 关键字
  9. use crate::sales_department::pre_sales_service;
  10.         
  11. mod manufacturing_department {
  12.     mod production_planning {
  13.         fn specify_production_plan() {
  14.             //add_to_production_order();
  15.             pre_sales_service::add_to_production_order();
  16.         }
  17.     }
  18. }
复制代码
究竟上 use 也可以直接将其全部路径都引入,像如许:
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         pub fn add_to_production_order() {}
  4.     }
  5. }
  6. mod manufacturing_department {
  7.     mod production_planning {
  8.    
  9.         use crate::sales_department::pre_sales_service::add_to_production_order;
  10.         fn specify_production_plan() {
  11.             add_to_production_order();
  12.             //pre_sales_service::add_to_production_order();
  13.         }
  14.     }
  15. }
复制代码
直接引入 add_to_production_order 函数的完备路径,如许的作法是答应的,而经常的做法会像之前一样,引入到其上一级,如许编写的代码会很明确看出来该函数是别的模块,而非当前模块。
4.6. as 关键字

如许引入完备路径会有什么问题吗?假设有两个同名函数不同模块,被同时引入呢?
答案是,rust 编译器将会告诉你如许的操作是不答应的。
那的确出现如许的情况怎么做?这时候 rust 提供了我们另外一个关键字 as,他可以为引入的变量或函数起别名,就像如许:
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         pub fn test_func() {}
  4.     }
  5.     pub mod after_sales_service {
  6.         pub fn test_func() {}
  7.     }
  8. }
  9. use crate::sales_department::pre_sales_service::test_func as pre_test_func;
  10. use crate::sales_department::after_sales_service::test_func as after_test_func;
  11. pub fn sales_announcement() {
  12.     pre_test_func();
  13.     after_test_func();
  14. }
复制代码
使用 as 关键字让两个原来会使用冲突的函数同时可以引入当前作用域。
4.7. pub use 重导出

  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         pub fn add_to_production_order() {}
  4.     }
  5. }
  6. pub use crate::sales_department::pre_sales_service;
  7. pub fn sales_announcement() {
  8.     pre_sales_service::add_to_production_order();
  9. }
复制代码
这段代码与之前有所不同,这里引入路径使用了 pub use 而非 use,如许的作用是为了让外部代码也可以用如许的路径。
使用 use 关键字
在使用 use 引入路径时,外部代码调用 add_to_production_order 需要指定其完备路径xiaomi_car::sales_department::pre_sales_service::add_to_production_order() 才可以或许调用该函数;
使用 pub use 关键字
当使用 pub use 引入时,外部代码则可以通过 xiaomi_car::pre_sales_service::add_to_production_order()来调用该函数,细致观察二者的区别,如许可以省略中间的一大堆具体路径,何乐而不为呢。
5. 引入的问题

5.1. 引入一个外部包

在之前我们编写了一个猜猜看游戏。那个项目使用了一个外部包,rand,来生成随机数。为了在项目中使用 rand,在 Cargo.toml 中参加了如下行:
  1. rand = "0.8.5"
复制代码
在 Cargo.toml 中参加 rand 依靠告诉了 Cargo 要从 crates.io 下载 rand 和其依靠,并使其可在项目代码中使用。
接着,为了将 rand 定义引入项目包的作用域,我们参加一行 use 起始的包名,它以 rand 包名开头并列出了需要引入作用域的项。回想一下之前的 “生成一个随机数” 部门,我们曾将 Rng trait 引入作用域并调用了 rand::thread_rng 函数:
  1. use rand::Rng;
  2. fn main() {
  3.     let secret_number = rand::thread_rng().gen_range(1..=100);
  4. }
复制代码
crates.io 上有很多 Rust 社区成员发布的包,将其引入你自己的项目都需要一道雷同的步调:在 Cargo.toml 列出它们并通过 use 将此中定义的项引入项目包的作用域中。
注意 std 尺度库对于你的包来说也是外部 crate。因为尺度库随 Rust 语言一同分发,无需修改 Cargo.toml 来引入 std,不外需要通过 use 将尺度库中定义的项引入项目包的作用域中来引用它们,好比我们使用的 HashMap:
  1. use std::collections::HashMap;
复制代码
这是一个以尺度库 crate 名 std 开头的绝对路径。
5.2. 嵌套路径来消除大量的 use 行

当需要引入很多定义于雷同包或雷同模块的项时,为每一项单独列出一行会占用源码很大的空间。比方猜猜看代码中有两行 use 语句都从 std 引入项到作用域:
  1. // --snip--
  2. use std::cmp::Ordering;
  3. use std::io;
  4. // --snip--
复制代码
相反,我们可以使用嵌套路径将雷同的项在一行中引入作用域。这么做需要指定路径的雷同部门,接着是两个冒号,接着是大括号中的各自不同的路径部门:
  1. // --snip--
  2. use std::{cmp::Ordering, io};
  3. // --snip--
复制代码
在较大的步伐中,使用嵌套路径从雷同包或模块中引入很多项,可以明显减少所需的独立 use 语句的数目!
我们可以在路径的任何层级使用嵌套路径,这在组合两个共享子路径的 use  语句时非常有用。比方有两个 use 语句:一个将 std::io 引入作用域,另一个将 std::io::Write 引入作用域:
  1. use std::io;
  2. use std::io::Write;
复制代码
两个路径的雷同部门是 std::io,这正是第一个路径。为了在一行 use 语句中引入这两个路径,可以在嵌套路径中使用 self:
  1. use std::io::{self, Write};
复制代码
这一行代码便将 std::io 和 std::io::Write 同时引入作用域。
5.3. 通过 glob 运算符将所有的公有定义引入作用域

如果盼望将一个路径下所有公有项引入作用域,可以指定路径后跟 *——glob 运算符:
  1. use std::collections::*;
复制代码
这个 use 语句将 std::collections 中定义的所有公有项引入当前作用域。使用 glob 运算符时请多加小心!Glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。
glob 运算符经常用于测试模块 tests 中,这时会将所有内容引入作用域。
6. 将模块拆分为多文件

到现在为止,所有的例子都在一个文件中定义多个模块。当模块变得更大时,你可能想要将它们的定义移动到单独的文件中,从而使代码更轻易阅读。
为了避免产生歧义,这里贴出笔者盼望拆分的原本代码:
  1. mod sales_department {
  2.     pub mod pre_sales_service {
  3.         fn model_consultation() {}
  4.         fn purchase_plan_options() {}
  5.         pub fn add_to_production_order() {}
  6.     }
  7.     pub mod after_sales_service {
  8.         fn vehicle_maintenance_services() {}
  9.         fn repair_services() {}
  10.     }
  11. }
  12. mod manufacturing_department {
  13.     mod production_planning {
  14.         fn specify_production_plan() {
  15.             crate::sales_department::pre_sales_service::add_to_production_order();
  16.         }
  17.     }
  18.     mod final_assembly_workshop {
  19.     }
  20. }
  21. pub fn sales_announcement() {
  22. sales_department::pre_sales_service::add_to_production_order();
  23. }
复制代码
首先尝试将 sales_department、manufacturing_department 模块拆分出去,首先在 src 目次下创建 sales_department.rs 文件,为其添加如下内容:
  1. pub mod pre_sales_service {
  2.     fn model_consultation() {}
  3.     fn purchase_plan_options() {}
  4.     pub fn add_to_production_order() {}
  5. }
  6. pub mod after_sales_service {
  7.     fn vehicle_maintenance_services() {}
  8.     fn repair_services() {}
  9. }
复制代码
[注]:这里是 src/sales_department.rs 文件
然后创建 manufacturing_department.rs 文件,为其添加如下内容:
  1. mod production_planning {
  2.     fn specify_production_plan() {
  3.         crate::sales_department::pre_sales_service::add_to_production_order();
  4.     }
  5. }
  6. mod final_assembly_workshop {
  7. }
复制代码
[注]:这里是 src/manufacturing_department.rs 文件
然后再修改 src/lib.rs 文件内容:
  1. mod sales_department;
  2. mod manufacturing_department;
  3. pub fn sales_announcement() {
  4. sales_department::pre_sales_service::add_to_production_order();
  5. }
复制代码
如许就完成了这两个模块的拆分。因为编译器找到了 crate 根中名叫 sales_department 的模块声明,它就知道去搜寻 src/sales_department.rs 这个文件。
那如果还想继续拆分呢?要怎么做,其实道理雷同,下面笔者展示将 sales_department 模块继续拆分成多个文件。
首先在 src 目次下创建 sales_department 目次,再进入 sales_department 目次,分别创建文件 pre_sales_service.rs、after_sales_service.rs,并为其添加如下内容:
  1. fn model_consultation() {}
  2. fn purchase_plan_options() {}
  3. pub fn add_to_production_order() {}
复制代码
[注]:这里是 src/sales_department/pre_sales_service.rs 文件
  1. fn vehicle_maintenance_services() {}
  2. fn repair_services() {}
复制代码
[注]:这里是 src/sales_department/after_sales_service.rs 文件
如许一来就将该模块继续拆分为更多的文件,如许拆分完后的文件目次布局如下。这个目次布局是不是很像我们的模块树。如果完全拆开,那么这就是模块树。
  1. im01@Ubuntu:xiaomi_car$ tree
  2. .
  3. ├── Cargo.lock
  4. ├── Cargo.toml
  5. └──  src
  6.     ├── lib.rs
  7.     ├── manufacturing_department
  8.     ├── manufacturing_department.rs
  9.     ├── sales_department
  10.     │   ├── after_sales_service.rs
  11.     │   └── pre_sales_service.rs
  12.     └── sales_department.rs
复制代码
各位应该看到, 这里笔者提前创建了一个 manufacturing_department 目次,各位同学可以自己尝试将 manufacturing_department 模块继续拆分。这个技巧让你可以在模块代码增长时,将它们移动到新文件中。
注意我们只需在模块树中的某处使用一次 mod 声明就可以加载这个文件。一旦编译器知道了这个文件是项目的一部门(并且通过 mod 语句的位置知道了代码在模块树中的位置),项目中的其他文件应该使用其所声明的位置的路径来引用那个文件的代码,这在“引用模块项目的路径”部门有讲到。换句话说,mod 不是你可能会在其他编程语言中看到的 "include" 操作。
7. 小结

Rust 提供了将包分成多个 crate,将 crate 分成模块,以及通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。你可以通过使用 use 语句将路径引入作用域,如许在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不外可以选择增长 pub 关键字使其定义变为公有。
这一小节的文章很长,笔者写的自以为也太过啰嗦,可以或许对峙看完的你真的很厉害,请收下笔者赠与的小红花
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

一给

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表