模块Module
在本章节,我们将深入讲讲Rust的代码构成单元: 模块,使用模块可以将包中的代码按照功能性进行重组,最终实现更好的可读性及易用性,同时还能非常灵活地去控制代码的可见性,进一步强化Rust的安全性
创建嵌套模块
使用cargo new --lib restaurant 创建一个小餐馆,注意,这里创建的是库类型的Package,然后将以下代码放入src/lib.rs中
rust
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() {}
}
}
以上的代码创建了三个模块,有几点需要注意的是
1.使用mod 关键字来创建新模块,后面紧跟着模块名称
2.模块可以嵌套; 这里的嵌套的原因是招待客人和服务都发生在前厅,因此我们的代码模拟来真实场景
3.模块中可以定义各种Rust 类型, 例如函数,结构体,枚举,特征等
4.所有模块均定义在同一个文件中
类似上述代码中所作的,使用模块,我们就能将功能相关的代码组织到一起,然后通过一个模块名称来说明这些代码为何被组织在一起,这样其它程序元在使用你的模块时,就可以更快地理解和上手。
模块树
在上一节中,我们提到过 src/main.rs 和 src/lib.rs 被成为包根(crate root) , 这个奇葩名称的来源是由于这两个文件的内容形成来一个模块crate ,该模块位于包的树形结构的根部:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
这颗树展示来模块自建彼此的嵌套关系,因此被成为模块树。其中create 包根是src/lib.rs 文件,包根文件中的三个模块分别星辰来模块树的剩余部分。
父子模块
如果模块 A 包含模块 B ,那么 A 是 B 的父模块, B 是 A 的子模块。 在上例中, front_of_house 是hosting 和 serving 的父模块,反之,后两者是前者的子模块,
聪明的读者,应该能联想到,模块树跟计算机上文件系统目录树的显示之处,不仅仅是组织结构上的相似,就连使用方式都很显示: 每个文件都有自己的路径,用户可以通过这些路径使用他们,在Rust中,我们也通过路径的方式来引用模块
用路径引用模块
想要调用一个函数,就需要知道它的路径,在Rust中, 这种路径有两种形式
1.绝对路径,从包根开始,路径名以包名或者crate 作为开头
2.相对路径, 从当前模块开始, 以self ,super 或当前某哎的标识符作为开头
让我们继续经营那个惨淡的小餐馆,这次为它实现一个小功能: 文件名 src/lib.rs
rust
mod front_of_house {
mod hosting {
fn add_to_waitlist(){}
}
}
pub fn eat_at_restaurant(){
// 绝对路径
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径
front_of_house::hosting::add_to_waitlist();
}
上面的代码为了简化实现,省去了其余模块和函数,这样可以把关注点放在函数调用上,eat_at_restaurant 是一个定义在包根中的函数,在该函数中使用来两种方式对 add_to_waitlist 进行调用
绝对路径引用
因为 eat_at_restaurant 和 add_to_waitlist 都定义在一个包中, 因此在绝对路径引用时,可以直接以create 开头,然后逐层引用,每一层自建使用:: 分隔:
crate::front_of_house::hosting::add_to_waitlist();
对比下之前的魔块树
crate
└── eat_at_restaurant
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
可以看出,绝对路径的调用,完全符合来模块树的层级递进,非常符合直觉,如果类比文件系统,就跟使用绝对路径调用可执行程序差不多: /front_of_house/hosting/add_to_waitlist ,使用crate作为开始就和使用 / 作为开始一样
相对路径引用
再回到模块树中,因为 eat_at_restaurant 和 front_of_house都处于包根 crate 中,因此相对路径可以使用 front_of_house 作为开头:
front_of_house::hosting::add_to_waitlist();
如果类比文件系统,那么它类似于调用同一个目录下的程序,你可以这么做:
front_of_house/hosting/add_to_waitlist ,嗯 也很符合直觉
绝对还是相对?
如果只是为来引用到指定模块中的对象,那么两种都可以,但是在实际使用是,需要遵循一个原则: 当代码被挪动位置时,尽量减少引用路径的修改,相信大家都遇到过,修改了某出代码,导致所有路径都要挨个替换,这显然不是好的路径选择。
回到之前的例子,如果我们把 front_of_house 模块 和eat_at_restaurant 移动到一个模块中customer_experience ,那么绝对路径的引用方式就必须进行修改: crate::customer_experience::front_of_house ... 但是假设我们使用过的相对路径,那么该路径就无需修改,因为他们两个的相对位置其实没有变:
crate
└── customer_experience
└── eat_at_restaurant
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
从新的模块树中,可以很清晰的看出这一点
在比如,其它的都不懂,把eat_at_restaurant 移动到 dining中,如果使用相对路径,你需要修改该路径,如果使用的是绝对路径,就无需修改
crate
└── dining
└── eat_at_restaurant
└── front_of_house
├── hosting
│ ├── add_to_waitlist
不过,如果不确定哪个好,你可以考虑有限使用绝对路径。 因为掉调用的地方和定义的地方往往是分离的,而定义的地方较少会变动
代码可见性
运行下面的代码(之前)
rust
mod front_of_house {
mod hosting {
fn add_to_waiflist() {}
}
}
pub fn eat_at_restaurant(){
// 绝对路径
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径
front_of_house::hosting::add_to_waitlist();
}
运行 cargo build 编译此库类型的Package ,意料之外的报错, 毕竟看上去确实很简单且没有任何问题:
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ private module
错误信息很清晰: hosting 模块是是有的,无法在包根进行访问,那么为何front_of_house 模块就可以访问? 因为它和 eat_at_restaurant 同属于一个包根作用域内,同一个模块内的代码自然不存在私有化问题(所以我们之前章节的代码都没有报过这个错误!)
模块不仅仅对于组织代码很有用,它还能定义代码的私有化边界: 在这个边界内,什么内容能让外界看到,什么内容不能,都有很明确的定义,因此,如果希望让函数或者结构体等类型编程私有化的,可以使用模块
Rust 出于安全的考虑,默认情况下,所有的类型都是私有化的,包括函数,方法,结构体,枚举,常量。 使得就连模块本身也是私有化的,在Rust 中,父模块完全无法访问子模块中的是有项,但是子模块却可以访问父模块,父父。。。模块的私有项。
pub关键字
类似其它语言的public 或者Go 语言中的首字母大写,Rust 提供来pub 关键字,通过它你可以控制模块和模块中指定项的可见性。
由于之前的解释,我们知道了只需要将hosting 模块标记为对外可见即可
rust
mod front_of_house {
pub mod hosting {
fn add_to_waitlist(){}
}
}
但是不幸的是,又报错了
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:12:30
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
哦 ? 难道模块可见还不够, 还需要将函数 add_to_waitlist 标记为可见的吗? 使得,没错,模块可见性不代表模块内部项的可见性,模块的可见性仅仅是允许其它模块去引用它 ,但是想要引用它内部的项,还得继续将对应的项标记为 pub 。
在实际项目中,一个模块需要对外暴露的数据和api 往往就寥寥数个,如果将模块标记为可见代表着内部项也全部对外可见,那你是不是还得把那些不可见的,一个一个标记为 private ? 反而是更麻烦得多。
既然知道了如何解决,那么我们为函数也标记上pub :
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist(){}
}
}
Bang, 顺利通过编译,感觉自己又变强来。
使用super引用模块
在用路径引用模块中,我们提到来相对路径又三种方式开始: self super 和 crate 或者模块名,其中,第三种在前面已经见到过, 现在来看看通过super 的方式引用模块项。
super 代表的是父模块为开始的引用方式,非常类似与文件系统中的 .. 语法,../a/b 文件名:
src/lib.rs
rust
fn serve_order(){}
// 厨房模块
mod back_of_house{
fn fix_incorrect_order() {
cook_order();
super::serve_order();
}
fn cook_order(){}
}
嗯,我们的小餐馆又完善了,终于有厨房来! 看来第一个客人也快可以有来.. 在厨房模块中,使用super::serve_order 语法,调用来父模块(包根) 中的serve_order函数。
那么你可能会问,为何不使用 create:serve_order 的方式? 额, 其实也可以,不过如果你确定未来的这种层级关系不会 改变。那么 super::serve_order 的方式会更稳定,未来就算他们都不再包根来,依然无需修改引用路径,所以路径的选用,往往还是取决于场景, 以及未来代码的可能走向。
使用self 引用模块
self 其实就是引用自身模块中的项,也就是说和我们之前章节的代码类似,都调用同一模块中的内容,区别在与之前章节中直接通过名称调用即可,而self ,你得多此一举
rust
fn serve_order(){
self::back_of_house::cook_order()
}
mod back_of_house {
fn fix_incorrect_order(){
cook_order();
crate::serve_order();
}
pub fn cook_order() {}
}
是的,多此一举,因为额完全可以直接调用 back_of_house ,但是 self 还有一个大用处,在下一届我们会讲
结构体和枚举的可见性
为啥要把结构体和枚举的可见性单独拎出来讲呢? 因为这两个家伙的成员字段拥有完全不同的可见性:
1.将结构体设置为 pub ,但它的所有字段依然是是有的。
2.将枚举设置为 pub ,它的所有字段也将对外可见
原因在于,枚举和结构体的使用方式不一样,如果枚举的成员对外不可见,那该枚举将一点用都没有,英因此枚举成员的可见性自动跟枚举可见性保持一直,这样可以简化用户的使用。
而结构体的引用场景比较复杂,其中的字段往往部分在A处被使用,部分在B处被使用,因此无法确定成员的可见性,那索性就设置为全部不可见,将选择权交给程序员。
模块与文件分离
在之前的例子中,我们所有的模块都定义在src/lib.rs 中,但是当模块变多或者变大时,需要将模块放入一个单独的文件中,让代码更好维护。
现在,把 front_of_house 前厅分离出来,放入一个单独的文件中, src/front_of_house.rs:
rust
pub mod hosting {
pub fn add_to_waitlist() {}
}
然后,将以下代码留在 src/list.rs中:
rust
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
so easy! 其实跟之前在同一个文件中也没有太大的不同, 但是有几点值得注意:
1.mod front_of_house; 告诉Rust 从另一个和模块 front_of_house 同名的文件中加载该模块的内容
2.使用绝对路径的方式来引用hosting 模块: crate::front_of_house::hosting;
需要注意的是,和之前代码中 mod front_of_house{..} 的完整模块不同,现在的代码中, 模块的生命和实现是分离的,实现是在单独的 front_of_house.rs 文件中, 然后通过mod front_of_house; 这条声明语句从该文件中把模块内容加载进来,因此我们可以认为,模块 front_of_house 的定义还是在src/lib.rs 中, 只不过模块的具体内容被移动到来src/front_of_house.rs文件中。
在这里出现来一个新的关键字use, 联想到其它章节我们见过的标准库引入use std::fmt; ,可以大致猜测,该关键值用来将外部模块中的项引入到当前作用域中来,这样无需冗长的父模块前缀即可调用: hosting::add_to_waitlist(); 在下节中,我们将对 use 进行详细讲解
当一个模块有许多子模块时,我们也可以通过文件夹的方式来组织这些子模块。
在上述例子中,我们可以创建一个目录 front_of_house ,然后在文件夹里创建一个 hosting.rs 文件。hosting.rs 文件现在就剩下:
rust
pub fn add_to_waitlist() {}
现在,我们尝试编译程序,很遗憾,编译器报错:
error[E0583]: file not found for module `front_of_house`
--> src/lib.rs:3:1
|
1 | mod front_of_house;
| ^^^^^^^^^^^^^^^^^^
|
= help: to create the module `front_of_house`, create file "src/front_of_house.rs" or "src/front_of_house/mod.rs"
是的,如果需要将文件夹作为一个模块,我们需要进行显示指定暴露哪些子模块,按照上述的报错信息,我们有两种方法:
1.在front_of_house 目录里创建一个 mod.rs ,如果你使用的rustc 版本在1.30之前,这是唯一的方法
2.在 front_of_house 同级目录里创建一个与模块(目录)同名的rs 文件, front_of_house.rs ,在新版本里,更建议使用这样的命名方式来避免项目中存在大量同名的mod.rs文件
如果使用第二种方式,文件结构将如下所示
src
├── front_of_house
│ └── hosting.rs
├── front_of_house.rs
└── lib.rs
而无论是上述哪个方式创建的文件,内容都是一样的,你需要在定义你(mod.rs 或 front_of_house.rs) 的子模块(子模块名与文件名相同);
rust
pub mod hosting;
使用 use 及受限可见性
如果代码中,通篇都是 crate::front_of_house::hosting::add_to_waitlist , 这样的函数调用形式,我不知道有谁会喜欢,也许靠代码行数赚工资的人会很喜欢,但是强迫症肯定受不了,悲伤的是程序员太多都有强迫症。。
因此我们需要一个办法来简化这种使用方式,在Rust 中,可以使用use 关键字把路径提前引入到当前作用域中,随后的调用就可以省略该路径,极大地简化来代码。
基本引入方式
在Rust中,引入模块中的项有两种方式,:绝对路径和相对路径,这两者在前面章节都有讲过,就不再赘述,先来看看使用绝对路径的引入方式
绝对路径引入模块
rust
mod front_of_house {
pub mod hosting{
pub fn add_to_waitlist(){}
}
}
usee crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
这里,我们使用 use 和绝对路径的方式,将hosting 模块引入到当前作用域中,然后只需通过hosting::add_to_waitlist 的方式,即可调用目标模块中的函数,相比,crate::front_of_house::hosting::add_to_waitlist() 的方式要简单的多,那么还能更简单吗?
相对路径引入模块中的函数
在下面的代码中,我们不仅要使用相对路径进行引入,而且与上面引入hosting模块不同,直接引入该模块中的 add_to_waitlist函数:
rust
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use front_of_house::hosting::add_to_waitlist;
pub fn set_at_restaurant() {
add_to_waitlist();
}
很明显,调用路径更短了。
引入模块还是函数
从使用简洁性来说,引入函数自然是更甚一筹,但是在某些时候,引入模块会更好:
- 需要引入同一个模块的多个函数
2.作用域中存在同名函数
在以上情况中,使用use front_of_house::hosting; 引入模块要比 use front_of_house::hosting::add_to_waitlist; 引入函数更好,
例如,如果想使用HashMap ,那么直接引入该结构体,是比引入模块更好的选择,因为在 collections 模块中,我们只需要使用一个hashmap 结构体:
rust
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1,2);
}
其实严格来说。对于引用方式并没有需要遵守的管理,主要赛事取决于你的喜好,不过我们建议,有限使用最细粒度(引入函数、结构体等) 的引用方式,如果引起来某种麻烦(例如前面两种情况),在使用引入模块的方式
避免同名引用
根据上一章节的你日欧那个,我们只要保证同一个模块中不存在同名项就行,模块自建,包之间的同名,谁管得着谁?,一起看下,如果遇到同名的情况该如何处理。
模块::函数
rust
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// -- snip --
}
fn function2() -> io::Result<()> {
// --snip--
}
上面的例子给出了很好的解决方案,使用模块引入的方式,具体的Rust 通过 模块::Result的方式进行调用
可以看出,避免同名冲突的关键,就是使用父模块的方式来调用,除此值往外,还可以给予引入的项起一个别名
as 别名引用
对于同名冲突玩儿逆天,还可以使用as关键字来决定,它可以赋予引入项一个全新的名称;
rust
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// -- snip --
}
fn function2() -> IoResult<()> {
// -- snip --
}
如上所示,首先通过 use std::io::Result 将Result 引入到作用域,然后使用as 给予它一个全新的名称 IoResult, 这样就不会再产生冲突:
Result 代表 std::fmt::Result;
IoResult 代表std::io::Result
引入项再导出
当外部的模块项A 被引入到当前模块中是,它的可见性自动被设置为是有的,如果你希望允许其它外部代码引用我们的模块项 A ,那么可以对它进行在导出:
rust
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();
}
如上,使用 pub use 即可实现,这里 use 代表引入 hosting 模块到当前作用域,pub 表示将该引入的内容再度设置为可见。
当你希望将内部的实现细节隐藏起来,或者按照某个目的组织代码时,可以使用 pub use 在导出,例如统一使用一个模块来提供对外的api , 那该模块就可以引入其它模块中的api ,然后在进行导出,最终对于用户来说,所有的api 都是由一个模块统一提供的。
使用第三方包
之前我们一直在引入标注库模块或者自定义模块,现在来引入下第三方包中的模块,关于如何引入外部以来,我们在Cargo 入门就有将,这里直接给出操作步骤
1.修改 Cargo.toml 文件, 在[dependencies] 区域添加一行: rand = "0.8.3"
- 此时,如果你用的是VSCode 和 rust-analyzer 插件,该插件会自动拉取该库,你可能需要等它完成后,在进行下一步(VSCode 左下角有提示)
好了, 此时,rand 包已经被我们添加到依赖中,下一步就是在代码中使用
rust
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1,101);
}
这里使用 use 引入了第三方包 rand 中的Rng 特征,因为我们需要调用的 gen_range 方法定义在该特征中。
crates.io,lib.rs
Rust社区已经为我们贡献了大量高质量的第三方包,你可以在crates.io 或者 lib.rs 中检索和使用,从目前来说查找包更推荐 lib.rs ,搜索功能更强大,内容展示也更加合理, 但是下载以来包还是得用crates.io.
你可以在网站上搜索 rand 包, 看看它的文档使用方式,是否和我们之前引入方式相一致: 在网上找到想要的包,然后将你想要的包和版本信息写入到cargo.toml 中。
使用 {} 简化引入方式
对于以下一行一行的引入方式:
rust
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::cmp::Ordering;
use std::io;
可以使用 {} 来一起引入进来,在大型项目中,使用这种方式来引入,可以减少大量 use 的使用
rust
use std::collections::{HashMap,BTreeMap,HashSet};
use std::{cmp::Ordering,io};
对于限免的同时引入模块和模块中的项:
rust
use std::io;
use std::io::Write;
可以使用 {} 的方式进行简化
rust
use std::io::{self,Write};
self
上面使用到了模块章节提到的self 关键字,用来替代模块自身,结合上一节中的self ,可以得出它在模块中的两个用途:
use self::XXX; 表示加载当前模块中的 xxx, 此时self 可省略
use xxx::{self,yyy} ,表示,加载当前路径下模块 xxx 本身,以及模块 xxx 下的yyy
使用 * 引入模块下的所有项
对于之前一行一行引入 std::collections 的方式,我们还可以使用
rust
use std::collections::*;
以上这种方式来引入 std::collections 模块下的所有公共项,这些公共项自然包含了 HashMap ,HashSet 等想手动引入的集合类型。
当使用 * 来引入的时候要格外小型,因为你很男子到到底那些被引入到来当前作用域中,有哪些会和你自己程序中的名称相冲突
rust
use std::collections::*;
struct HashMap;
fn main() {
let mut v = HashMap::new();
v.insert("a",1);
}
以上代码中, std::collections::HashMap 被 * 引入到当前作用域,但是由于存在另一个同名的结构体,因此HashMap::new 根本不存在, 因为对于编译器来说,本地同名类型的优先级更高。
在实际项目中, 这种引用方式往往用于快速写测试代码,它可以把所有东西依次性引入到tests模块中。
受限的可见性
在上一节中,我们学习来可见性这个概念,这也是模块体系中最为核心的概念,控制来模块中哪些内容可以被外部看见,但是在实际使用时,光被外面看到还不行,我们还想控制哪些人能看,这就是Rust提供的受限可见性。
例如,在Rust中,包是一个模块树,我们可以通过 pub(crate) item; 这种方式来实现: item 虽然是对外可见的,但是只在当前包内可见,外部包无法引用到该 item 。
所以,如果我们想要让某一项可以在整个包中都可以被使用, 那么有两种办法:
在包根中定义一个非 pub 类型的 x ( 父模块的项对子模块都是可见的,因此包根中的项对模块树上的所有模块都可见)
在子模块中定义一个pub 类型 的y ,同时通过use 将其引入到包根
rust
mod a {
pub mod b {
pub fn c(){
pringln!("{:?}",crate::X);
}
#[derive(Debug)]
pub struct Y;
}
}
#[derive(Debug)]
struct X;
use a::b::Y;
fn d() {
println!("{:?}",Y);
}
以上代码充分说明了之前两种办法的使用方式,但是有时我们会遇到这两种方法都不太好用的时候,例如希望对于某些特定的模块可见,但是对于其它模块又不可见
rust
pub mod a {
pub const I: i32 = 3;
fn semisecret(x: i32) -> i32 {
use self::b::c::I;
x + J
}
pub fn bar(z:i32) -> i32 {
semisecret(I) * z
}
pub fn foo(y: i32) -> i32 {
semisecret(I) + y
}
mod b {
mod c {
const J: i32 =4;
}
}
} // 以上代码完全看不懂, 一会i 一会 J 一会z 一会y 😫
这段代码会报错,因为与父模块中的项对子模块可见相反,子模块中的项对父模块是不可见的,这里semisecret 方法中, a -> b -> c 形成来父子模块链,那c 中的J 自然对 a 模块不可见。
如果使用之前的可见性方式,那么想保持 J 是有,同时让a 继续使用 semisecret 函数的办法是将该函数移动到 c 模块中,然后使用 pub use 将semisecret 函数进行再导出:
rust
pub mod a {
pub const I: i32 = 3;
use self::b::semisecret;
pub fn bar(z:i32) -> i32 {
semisecret(I) * z
}
pub fn foo(y: i32) -> i32 {
semisecret(I) + y
}
mod b {
pub use self::c::semisecret;
mod c {
const J:i32 =4;
pub fn semisecret(x:i32) -> i32 {
x + J
}
}
}
}
这段代码说实话问题不大,但是有些破坏来我么年之前的逻辑,如果项保持代码逻辑,同时又让J在 a 内可见该怎么办?
rust
pub mod a {
pub const I: i32 = 3;
fn semisecret(x:i32) -> i32 {
use self::b::c::J;
x + J
}
pub fn bar(z:i32) -> i32 {
semisecret(I) * z
}
pub fn foo(y: i32) -> i32{
semisecret(I) + y
}
mod b {
pub (in crate::a) mod c {
pub (in crate::a) const J:i32 = 4;
}
}
}
通过 pub(in crate::a) 的方式,我们指定了模块 c 和常量 J 的可见范围都是a 模块中, a 之外的模块是完全访问不到他们的。
限制可见性语法
pub (crate) 或 pub(in crate::a) 就是限制可见性语法, 前者是限制在整个包内可见, 后者是通过绝对路径,限制在包内的某个模块可见。总结一下。
pub 意味着可见性无任何限制
pub(crate) 表示在当前包可见
pub(self) 在当前模块可见
pub(super) 在父模块可见
pub (in <path>) 表示在某个路径代表的模块中可见,其中path 必须是父模块或者祖先模块
一个综合例子
rust
// 一个名为 my_mod 的模块
mod my_mod {
// 模块中的项 默认具有是有的可见性
fn private_function() {
println!("called my_mod::private_function()");
}
// 使用 pub 修饰语 来改变默认可见性
pub fn function() {
println!("called my_mod::function()");
}
// 在同一模块中,项可以访问其它项,即使它是私有的。
pub fn indirect_access() {
print!("called my_mod::indirect_access(), that\n>");
private_function();
}
// 模块可以嵌套
pub mod nested {
pub fn function() {
println!("called my_mod::nested::function()");
}
#[allow(dead_code)]
fn private_function() {
println!("called my_mod::nested::private_function()");
}
// 使用 pub(in path) 语法定义的函数只在给定的路径中可见。
// path 必须是父模块 (parent module) 或 祖先模块 (ancestor module)
pub(in crate::my_mod) fn public_function_in_my_mod() {
print!("called my_mod::nested::public_function_in_my_mod(),that \n>");
public_function_in_nested()
}
// 使用 pub(self) 语法定义的函数则只在当前模块中可见。
pub(self) fn public_function_in_nested(){
printlin!("called my_mod::nested::public_function_unnested");
}
// 使用 pub(super) 语法定义的函数只在父模块中可见。
pub(super) fn public_function_in_super_mod(){
println!("called my_mod::nested::public_function_in_super_mod");
}
}
pub fn call_public_function_in_my_mod(){
print!("called my_mod::call_public_function_in_my_mod(),that \n> ");
nested::public_function_in_my_mod();
print!(">");
nested::public_function_in_super_mod();
}
// pub(crate) 使得函数只在当前包中可见
pub(crate) fn public_function_in_crate() {
println!("called my_mod::public_function_in_crate()");
}
// 嵌套模块的可见性 遵循相同的规则
mod private_nested {
#[allow(dead_code)]
pub fn function() {
println!("called my_mod::private_nested::function()");
}
}
}
fn function() {
println!("called function()");
}
fn main() {
// 模块极致消除来相同名字的项 之间的歧义
function();
my_mod::function();
// 公有项,包括嵌套模块内的,都可以在父模块外部访问。
my_mod::indirect_access();
my_mod::nested::function();
my_mod::call_public_function_in_my_mod();
// pub(crate) 项 可以在同一个 crate 中的任何地方访问
my_mod::public_function_in_crate();
// pub(in path) 项只能在指定的模块中访问
// 报错! 函数 public_function_in_my_mod 是是有的
// my_mod::nested::public_function_in_my_mod();
// 试一试 ^ 取消改行的注解。
// 模块的私有项 不能直接访问 ,即便它是嵌套在公有模块内部的
// 报错! private_function 是是有的
// my_mod::private_functin();
// 报错! private_function 是是有的
// my_mod::nested::private_function();
// 报错 private_nested 是是有的
// my_mod::private_nested::function();
}