目录
- 一、简单概念
- 二、crate和包
- 2.1 crate规则
- 2.2 包规则
- 2.3 Cargo的遵循的一些约定
- 2.4 控制模块的作用域和私有性
- 1) 模块
- 2)引用模块树中的项
- 3)使用 super 起始的相对路径
- 4) 公有结构体和枚举
- 三、use关键字的使用
- 四、分割模块进入不同的文件
一、简单概念
Rust 有许多功能可以管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这有时被称为 模块系统(the module system)
,包括:
- 包(Packages): Cargo 的一个功能,它允许
构建、测试和分享 crate
; - Crates: 一个模块的树形结构,它形成了库或二进制项目;
- 模块(Modules)和 use: 允许
控制作用域和路径的私有性
; - 路径(path):一个命名例如结构体、函数或模块等项的方式;
二、crate和包
2.1 crate规则
- crate 是一个
二进制项或者库
; - crate root是一个源文件,Rust 编译器以它为起始点构成 crate 的根模块;
- 包(package)是提供一系列功能的
一个或者多个crate
; - 一个包会包含一个
Cargo.toml文件
;
2.2 包规则
- 一个包中至多只能包含一个库crate(library crate);
- 包中可以包含任意多个二进制 crate(binary crate);
- 包中至少包含一个crate,无论是库的还是二进制的;
2.3 Cargo的遵循的一些约定
- 创建项目时,src/main.rs 就是一个与包同名的二进制crate的crate根;
- 如果包目录中同时包含src/lib.rs,则有两个crate(一个库,一个二进制),且crate的名字都与包名相同;
- 将文件放在src/bin 目录下,一个包可以拥有多个二进制crate,目录下的每个文件都会被编译成一个独立的二进制 crate;
- 一个crate会将一个作用域内的相关功能分组到一起,这样就可以很方便地在多个项目之间共享;
2.4 控制模块的作用域和私有性
1) 模块
- 模块可以让我们将一个crate中的代码进行分组,以提高可读性与重用性;
- 模块还可以控制项的私有性,即项是可以被外部代码使用的(public),还是作为一个内部实现的内容,不能被外部代码使用(private);
- 模块可以包含模块,也可以包含结构体、枚举、常量、trait或者函数;
下面的模块定义了酒店前台,其中有两个子模块,分别为hosting
和serving
。新建一个src/lib.rs
文件,将下面的代码粘贴进去。
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() {}
}
}
- src/main.rs和src/lib.rs被称为crate根,这两个文件中任意一个的内容会构成名为crate的模块,且该模块位于crate的被称为
模块树
的模块结构的根部; - 结构如下
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
- 最顶部的crate模块是隐式存在的;
- 结构展示了模块之间的
嵌套关系
(hosting在front_of_house内部)以及兄弟关系
(hosting与serving)
2)引用模块树中的项
路径有两种形式:
绝对路径: 从 crate 根部开始,以crate名或者字面量 crate 开头;
相对路径: 从当前模块开始,以self、super或当前模块的标识符开头;
绝对路径和相对路径都后跟一个或多个由双冒号(::)分割的标识符
代码示例
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();
}
- crate是隐式的顶层模块,因此
绝对路径从crate开始
; - add_to_waitlist与eat_at_restaurant在同一crate下,因此相对路径从当前文件下的
front_of_house
开始; - 相对路径与绝对路径的使用依据具体的项目确定,但由于把代码定义和项调用各自独立地移动是更常见的,因此
更倾向于使用绝对路径
;
编译后发现编译不过
- 报错信息说hosting模块是私有的,因此
模块内部默认都是私有的
; - 事实上,Rust中默认所有项
(函数、方法、结构体、枚举、模块和常量)
都是私有的; - 由于子模块封装并隐藏了他们的实现详情,但是子模块可以看到他们定义的上下文,因此
父模块中的项不能使用子模块中的私有项
,但是子模块中的项可以使用父模块中的项; - 子模块可以通过使用
pub
关键字创建公共项,使子模块的内部部分暴露给上级模块; - 添加
pub
关键字的模块并不使其内容也是公有的,模块上的 pub 关键字只允许其父模块引用它;
将hosting改为公有
mod front_of_house {
pub 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();
}
编译之后发现add_to_waitlist
并非公有。
因此,将add_to_waitlist()
也变成公有的,外部才能访问
mod front_of_house {
pub mod hosting {
pub 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和front_of_house在同一个文件里都属于根级,属于根级的可以互相调用而无论是公有的还是私有的
3)使用 super 起始的相对路径
- 可以使用super开头来构建从
父模块开始
的相对路径; fix_incorrect_order
函数在back_of_house
模块中,所以可以使用super
进入back_of_house
父模块(即顶层crate)
fn serve_order(){}
mod back_of_house {
fn fix_incorrect_order(){
cook_order();
super::serve_order(); //上级路径(模块外)下的serve_order
crate::serve_order(); //绝对路径访问
}
fn cook_order(){}
}
4) 公有结构体和枚举
- 可以使用
pub
来定义公有结构体和枚举; - 定义为
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"),
}
}
}
}
fn eat_at_restaurant() {
// 在夏天点一份黑麦面包作为早餐
let mut meal = back_of_house::Breakfast::summer("Rye");
// 更改我们想要的面包
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// 如果取消下一行的注释,将会导致编译失败
// 不得更改随餐搭配的季节水果
// meal.seasonal_fruit = String::from("blueberries");
}
fn main(){
eat_at_restaurant();
}
- 代码中
Breakfast
结构体以及内部的toast为公有的,季节水果字段seasonal_fruit
是私有的,不允许被外部修改 - 由于
back_of_house::Breakfast
具有私有字段,所以这个结构体需要提供一个关联函数来构造实例; - 如果没有这样的函数,由于不能在
eat_at_restaurant
中设置私有字段seasonal_fruit
的值,因此无法在eat_at_restaurant
中创建Breakfast实例;
枚举的公有用法
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
三、use关键字的使用
使用 use 关键字将路径一次性引入作用域
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
pub fn other_function(){}
}
}
use crate::front_of_house::hosting;
// use front_of_house::hosting; //可以使用相对路径
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::other_function();
}
- 使用use之后可以简写
eat_at_restaurant
中对于hosting模块内部函数的调用; - 通过use引入作用域的路径依然会检查私有性;
use的习惯用法
- 将函数的父级模块引入作用域时通常指定到父级,这样可以清晰的表明
函数不是在本地定义的
; - struct、enum等引入作用域时通常指定完整路径(指定到本身)
- 同名条目:指定到父级
use std::io;
use std::fmt;
use std::collections::HashMap;
//返回不同的Result
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
fn main() {
let mut map = HashMap::new(); //明确表示是外部引入的HashMap
map.insert(1, 2);
}
使用use关键字定义别名
在这个类型的路径后面,可以使用as关键字指定一个新的本地名称或者别名
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
使用pub use重导出名称
- 使用use将路径(名称)导入到作用域内后,该名称在此作用域内是私有的;
- 为了让调用此代码的代码能在自己的作用域中引用这些类型,需要使用pub use进行
重导出(re-exporting)
;
mod front_of_house{
pub mod hosting{
pub fn add_to_waitlist(){}
}
}
pub use crate::front_of_house::hosting; //加上pub后,外部也可以看到这个hosting了
pub fn eat_at_restaurant(){
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
- 加上pub use后,可以通过新路径
hosting::add_to_waitlist
来调用add_to_waitlist函数; - 如果没加,则外部模块不能使用这个新路径;
使用外部包
比如要使用rand合成随机数,则在Cargo.toml
文件中加入
[dependencies]
rand = "0.8.3"
使用
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..101);
}
路径嵌套
- 下面的代码可以写成
use std::{cmp::Ordering, io, collections::HashMap};
use std::collections::HashMap;
use std::io;
use std::cmp::Ordering;
- 下面的代码可以写成
use std::io::{self, Write};
use std::io;
use std::io::Write;
- 可以使用
"*"
运算符引入路径下的所有公有项
use std::collections::*;
- 这通常用于测试模块
tests
中; - 一般用于
预导入(prelude)
模式;
四、分割模块进入不同的文件
文件src/lib.rs
的内容为
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
pub fn other_function(){}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::other_function();
}
如果变成
mod front_of_house;
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::other_function();
}
则需要以下步骤
- 创建模块
front_of_house
的同名文件名front_of_house.rs
,内容为
pub mod hosting {
pub fn add_to_waitlist() {}
pub fn other_function(){}
}
即可。
- 如果要更进一步将
front_of_house.rs
文件的内容改为pub mod hosting;
,则还需创建src/front_of_house/hosting.rs
文件,内容为
pub fn add_to_waitlist() {}
pub fn other_function() {}
- 只做到第1步比较简单,这里贴一下第二步的目录结构
各个文件的值如下
//lib.rs文件内容
mod front_of_house;
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::other_function();
}
//front_of_house.rs文件内容
pub mod hosting;
//hosting.rs文件内容
pub fn add_to_waitlist(){}
pub fn other_function(){}
最复杂的这种分割方法保留了原本的模块树结构