- 包:一个用于构建、测试并分享单元包的Cargo功能;
- 单元包:一个用于生成库或可执行文件的树形模块结构;
- 模块及use关键字:被用于控制文件结构、作用域及路径的私有性;
- 路径:一种用于命名条目的方法,这些条目包括结构体、函数和模块等。
1、包与单元包:
单元包可以被用于生成二进制程序或库。将Rust编译时所使用的入口文件称作这个单元包的根节点,它同时也是单元包的根模块。
包由一个或多个提供相关功能的单元包集合而成,它所附带的配置文件Cargo.toml
描述了如何构建这些单元包的信息。
包包含的规则:
- 一个包中只能拥有最多一个库单元包;
- 包可以拥有任意多个二进制单元包;
- 包内必须存在至少一个单元包(库单元包或二进制单元包)。
Cargo会默认将src/main.rs
视作一个二进制单元包的根节点而无需指定,这个二进制单元包与包拥有相同的名称。当包中同时存在src/main.rs
和src/lib.rs
时,就会分别存在一个二进制单元包与一个库单元包,它们拥有与包相同的名称,可以在路径src/bin
下添加源文件来创建出更多的二进制单元包,这个路径下的每个源文件都会被视作单独的二进制单元包。
单元包可以将相关的功能分组,并放到同一作用域下,这样便可以使这些功能轻松地在多个项目中共享。
将单元包的功能保留在它们自己的作用域中有助于指明某个特定功能来源于哪个单元包,并避免可能的命名冲突。
2、通过定义模块来控制作用域及私有性:
模块允许将单元包内的代码按照可读性与易用性来进行分组,同时还允许控制条目的私有性。即模块决定了一个条目是否可以被外部代码使用(公用),或者仅仅只是一个内部的实现细节而不对外暴露(私有)。
模块内可以继续定义其他模块,也同样可以包含其他条目的定义,例如:结构体、枚举、常量、trait等。
在Rust中src/main.rs
和src/lib.rs
被称为单元包的根节点,这两个文件的内容各自组成了一个名为crate
的模块,并位于单元包模块结构的根部。这个模块结构也被称为模块树
。
模块树的结构:
当模块A被包含在模块B内时,将模块A称为模块B的子节点,模块B称为模块A的父节点。
3、用于在模块树中指明条目的路径:
为了在Rust的模块树中找到某个条目,同样需要使用路径。例如,在调用某个函数的时候,必须知道它的路径。
路径有两种形式:
- 使用单元包名或字面量crate从根节点开始的绝对路径;
- 使用self、super或内部标识符从当前模块开始的相对路径。
绝对路径与相对路径都由至少一个标识符组成,标识符之间使用双冒号::
分隔。
Rust中的所有条目(函数、方法、结构体、枚举、模块及常量)默认都是私有的。处于父级模块中的条目无法使用子模块中的私有条目,但子模块中的条目可以使用它所有祖先模块中的条目。虽然子模块包装并隐藏了自身的实现细节,但它却依然能够感知当前定义环境中的上下文。
(1)、使用pub关键字来暴露路径:
为了能够让父模块中可以正常访问子模块中的函数,可以使用关键字pub
来标记函数。示例:
mod front_of_house{
pub mod hosting{
pub add_to_waitlist(){}
}
}
pub fn eat_at_restaurant() {
// 绝对路径
crate::front_of_house::hosting::add_to_waitlist();
// 相对路径
front_of_house::hosting::add_to_waitlist();
}
注意:仅将父模块设为pub,在访问时,子模块仍是私有的,对外不可见,需要将要调用的最终模块也设置为pub。一个模块的同级节点之间的访问不需要使用关键字pub
。
(2)、使用super关键字开始构造相对路径:
可以从父模块开始构造相对路径,这一方式需要在路径起始处使用super关键字。
示例:
fn serve_order(){}
mod back_of_house {
fn fix_incorrect_order(){
cook_order();
super::serve_order();
}
}
由于fix_incorrect_order函数处于back_of_house模块内,所以可以使用super关键字来跳转至back_of_house的父模块,也就是根模块处。从它开始,可以成功地找到 serve_order。
(3)、将结构体或枚举声明为公共的:
结构体和枚举都可以使用pub
来声明为公共的,但是二者存在一定的差异。当在结构体前面使用pub
时,结构体本身就成为了公共结构体,但是它的字段依旧保持了私有状态。可以逐一决定是否将某个字段公开。
示例:
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"),
}
}
}
}
这里的toast是公共的,所以可以在别的函数中使用点号读写,但是seasonal_fruit字段是私有的,不可以直接使用点号进行读写。因为back_of_house::Breakfast拥有了一个私有字段,所以这个结构体需要提供一个公共的关联函数来构造Breakfast的实例(也就是本例中的summer)。如果缺少了这样的函数,将无法在别的函数中中创建任何的Breakfast实例。
与结构体不同的是,将一个枚举声明为公共的时,它所有的变体都自动变为了公共的,仅需要在enum关键字前放置pub。
示例:
mod back_of_house{
pub enum Appetizer{
Soup,
Salad,
}
}
这里的Soup和Salad都具有公共属性。
4、用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();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
使用use来指定相对路径稍有一些不同。必须在传递给use的路径的开始处使用关键字self,而不是从当前作用域中可用的名称开始。
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use self::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
(1)、创建use路径时的惯用模式:
在使用关键字use
指定函数路径时,只指定到函数的父模块,这意味着在调用函数时必须指定这个父模块,从而更清晰地表明当前函数有没有被定义在当前作用域,同样也能避免重复路径。
(2)、使用as关键字来提供新的名称:
使用use将同名类型引入作用域时所产生的问题还有另外一种解决办法:可以在路径后使用as关键字为类型指定一个新的本地名称,也就是别名。
示例:
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --略
--
}
fn function2() -> IoResult<()> {
// --略
--
}
(3)、使用pub usb重导出名称:
当我们使用use
关键字将名称引入作用域时,这个名称会以私有的方式在新的作用域中生效。为了让外部代码能够访问到这些名称,可以通过组合使用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();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
(4)、使用嵌套的路径来清理众多use语句:
当想要使用同一个包或同一个模块内的多个条目时,将它们逐行列出会占据较多的纵向空间。可以在同一行内使用嵌套路径来将上述条目引入作用域。这一方法需要首先指定路径的相同部分,再在后面跟上两个冒号,接着用一对花括号包裹路径差异部分的列表。
示例:
use std::cmp::Ordering;
use std::io;
// ---略
可以写成:
use std::{cmp::Ordering, io};
// ---略
同理:
use std::io;
use std::io::Write;
可以写成:
use std::io::{self, Write};
(5)、通配符:
假如想要将所有定义在某个路径中的公共条目都导入作用域,那么可以在指定路径时在后面使用*
通配符。
示例:
use std::collections::*;
上面这行use语句会将定义在std::collections
内的所有公共条目都导入当前作用域。