使用包、crate和模块管理不断增长的项目
-
- 1.包和crate
- 2.定义模块来控制作用域与私有性
-
- 2.1模块概括
-
- 1.从crate根节点开始
- 2.声明模块
- 3.声明子模块
- 4.模块中的代码路径
- 5.私有 vs 公用
- 6.use关键字
- 2.2在模块中对相关代码进行分组
-
- 1.一个包含了其他内置函数的模块front_of_house模块
- 3.引用模块项目的路径
-
- 3.1引用模块项目的路径
-
- 3.2使用pub关键字暴露路径
- 3.2super开始的相对路径
- 3.3创建公有的结构体和枚举
-
- 4.使用use关键字将路径引入作用域
-
- 4.1使用use关键字
-
- 1.使用use将模块引入作用域
- 2.use语句只适用于其所在的作用域
- 4.2创建惯用的use路径
-
- 1.使用use将add_to_waitlist函数引入作用域,这并不符合习惯
- 2.将HashMap引入作用域的习惯用法
- 3.使用父模块将两个具有相同名称的类型引入同一作用域
- 4.3使用as关键字提供新的名称
- 4.4使用pub use重导出名称
- 4.5使用外部包
- 4.6嵌套路径来消除大量的use行
-
- 1.指定嵌套的路径在一行中将多行带有相同前缀的项引入作用域
- 2.通过两行use语句引入两个路径,其中一个时另一个的子路径
- 3.部分重复的路径合并为一个use语句
- 4.通过glob运算符将所有的公有定义引入作用域
- 5.将模块拆分成多个文件
-
- 1.模块拆分
-
- 1.1声明front_of_house模块,其内容将位于src/front_of_house.rs
- 1.2在src/front_of_house.rs中定义front_of_house模块
- 1.3子模块的执行代码
- 2.注意事项
1.包和crate
- crate是Rust在编译时最小的代码单位
- crate有两种形式: 二进制项 和 库
- 库并没有main函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西
- crate root是一个源文件,Rust编译器以它为起始点,并构成你的crate的根模块
- 包中可以包含至多一个库;包中可以包含任意多个二进制crate,但是必须至少包含一个crate(无论是库还是二进制的)
- 包是提供一些列功能的一个或多个crate,一个包回包含一个Cargo.toml文件
2.定义模块来控制作用域与私有性
2.1模块概括
1.从crate根节点开始
- 当编译一个crate,编译器首先在crate根文件(对于一个库crate而言是src/lib.rs,对于一个二进制crate而言是src/main.rs)中寻找需要被编译的代码
2.声明模块
- 在crate根文件中,你可以声明一个模块;比如,使用mod garden声明了一个叫做garden的模块;编译器会在下列路径中寻找模块代码
- 内联,在大括号中,当mod garden后方不是一个分号而是一个大括号
- 在文件 src/garden.rs
- 在文件 src/garden/mod.rs
3.声明子模块
- 在除了crate根节点以外的其他文件中,你可以定义子模块;比如,使用src/garden.rs中定义了mod vegetables;编译器会在以父模块命名的目录中寻找子模块代码
- 内联,在大括号中,当mod vegetables后方不是一个分号而是一个大括号
- 在文件 src/garden/vegetables.rs
- 在文件 src/garden/vegetables/mod.rs
4.模块中的代码路径
- 一旦一个模块是你crate的一部分,可以在隐私规则允许的前提下,从同一个crate内的任意地方,通过代码路径引用该模块的代码
- 一个garden vegetables模块下的Aspargus类型可以在crate::garden::vegetables::Asparagus被找到
5.私有 vs 公用
- 一个模块里的代码默认对其父模块私有
- 为了使一个模块公用,应当在声明时使用pub mod 替代 mod
- 为了使一个公用模块内部的成员公用,应当在声明前使用pub
6.use关键字
- 在一个作用域内,use关键字创建了一个成员的快建方式,用来减少长路径的重复
- 在任何可以引用crate::garden::vegetables::Asparagus的作用域,通过use crate::garden::vegetables::Asparagus
- 创建一个快捷方式,然后你就可以在作用域中只写Asparagus来使用该类型
backyard
|-Cargo.lock
|-Cargo.toml
|-src
|-garden
| |-vegetables.rs
|-garden.rs
|-main.rs
src/garden/vegetables.rs
#[derive(Debug)]
pub struct Asparagus {}
src/garden.rs
pub mod vegetables;
src/main.rs
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {:?}!",plant);
}
2.2在模块中对相关代码进行分组
- 模块让我们可以将一个crate中的代码进行分组,以提高可读性与重用性
- 一个模块中的代码默认是私有的,还可以利用模块控制项的私有性
- 可以将函数放置到嵌套的模块中,来使crate结构与实际餐厅结构相同
- 通过执行cargo new --lib restaurant,来创建一个新的名为restaurant的库
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.一个包含了其他内置函数的模块front_of_house模块
- src/main.rs和src/lib.rs叫做crate根
crate
|-front_of_house
|-hosting
| |-add_to_waitlist
| |-seat_at_table
|-serving
|-take_order
|-serve_order
|-take_payment
3.引用模块项目的路径
3.1引用模块项目的路径
1.路径
- 绝对路径
- 以crate根(root)开头的全路径;对于外部crate的代码,是以crate名开头的绝对路径,对于当前crate的代码,则以字面值crate开头
- 相对路径
- 从当前模块开始,以self、super或当前模块的标识符开头
- 绝对路径和相对路径都后跟一个或多个由双冒号(::)分割的标识符
########## error code ####################
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist();
front_of_house::hosting::add_to_waitlist();
}
2.模块访问规则
- 在Rust中,默认所有项(函数、方法、结构体、枚举、模块和常量)对父模块都是私有的;希望创建一个私有函数或结构体,可以将其放在一个模块
- 父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用它们父模块中的项
3.2使用pub关键字暴露路径
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
pub fn eat_at_restaurant() {
crate::front_of_house::hosting::add_to_waitlist();
front_of_house::hosting::add_to_waitlist();
}
3.2super开始的相对路径
- 通过在路径的开头使用super,从父模块开始构建相对路径,而不是从当前模块或crate根开始
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
3.3创建公有的结构体和枚举
1.结构体
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"),
}
}
}
}
pub fn eat_at_restaurant() {
let mut meal = back_of_house::back_of_house::summer("Rye");
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
}
2.枚举
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;
}
4.使用use关键字将路径引入作用域
4.1使用use关键字
1.使用use将模块引入作用域
- create::front_of_house::hosting模块引入了eat_at_restaurant函数的作用域
- 需要指定hosting::add_to_waitlist即可在eat_at_restaurant中调用add_to_waitlist函数
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();
}
2.use语句只适用于其所在的作用域
- 作用域中use和路径类似于在文件系统中创建软连接(符号连接,symbolic link)
- 通过在crate根增加use crate::front_of_house::hosting,hosting在作用域中就是有效的名称
- 通过use引入作用域的路径也会检查私有性,同其他路径一样
############### 编译器错误显示短路不在适用于customer #####################
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
- 为了修复这个问题,可以将use移动到customer模块内,或者在子模块customer内通过super::hosting引用父模块中的这个短路径
use crate::front_of_house::hosting;
mod customer {
super::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
4.2创建惯用的use路径
1.使用use将add_to_waitlist函数引入作用域,这并不符合习惯
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
mod customer {
pub fn eat_at_restaurant() {
add_to_waitlist();
}
}
2.将HashMap引入作用域的习惯用法
- 使用use引入结构体、枚举和其他项时,习惯是指定它们的完整路径
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
3.使用父模块将两个具有相同名称的类型引入同一作用域
use std::fmt;
use std::io;
fn function1() -> fmt::Result{}
fn function2() -> io::Result{}
4.3使用as关键字提供新的名称
- 使用use将两个同名类型引入同一作用域这个问题还是另一个解决办法
- 在这个类型的路径后面,使用as指定一个新的本地名称或者别名
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {}
fn function2() -> IoResult {}
4.4使用pub use重导出名称
- 使用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();
}
- 未使用pub use之后,外部代码需要使用路径 restaurant::front_of_house::hosting::add_to_waitlist()来调用add_to_waitlist函数;外部代码现在可以使用路径restaurant::hosting::add_to_waitlist
4.5使用外部包
- 在Cargo.toml中加入rand依赖告诉了Cargo要从crate.io下载rand和其依赖,并使其可在项目代码中使用
- Cargo.toml
[package]
name = "randNum"
version = "0.1.0"
edition = "2021"
[dependencies]
rand = "0.8.5"
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("随机数:{}", secret_number);
}
- 注意 std 标准库对于你的包来说也是外部crate
- 标准库随Rust语言一同分发,无需修改Cargo.toml来引入 std
use std::collections::HashMap;
4.6嵌套路径来消除大量的use行
1.指定嵌套的路径在一行中将多行带有相同前缀的项引入作用域
- 当需要引入很多定义于相同包或相同模块的项时,为每一项单独列出一行占用源码很大的空间
use std::cmp::Ordering;
use std::io;
use std::{::cmp::Ordering,io};
2.通过两行use语句引入两个路径,其中一个时另一个的子路径
use std::io;
use std::io::write;
3.部分重复的路径合并为一个use语句
use std::io::{self,Write};
4.通过glob运算符将所有的公有定义引入作用域
- 所有公有项引入作用域,可以指定路径后跟*,glob运算符
use std::collections::*;
5.将模块拆分成多个文件
1.模块拆分
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();
}
1.1声明front_of_house模块,其内容将位于src/front_of_house.rs
mod front_of_house;
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
1.2在src/front_of_house.rs中定义front_of_house模块
pub mod hosting;
1.3子模块的执行代码
- src/front_of_house/hosting.rs
pub fn add_to_waitlist(){}
2.注意事项
- 一种老版本模块(目前也受支持)
- src/front_of_house/mod.rs
- src/front_of_house/hosting/mod.rs
- 和以上新方式对比,了解不同