伴随着项目的增长,通过将代码分解为多个模块和多个文件来组织代码
伴随着包的增长,将包中部分代码提取出来,做成独立 crate,即外部依赖项
一个包可以包含多个二进制 crate 项和一个可选的 crate 库。
包(Packages):
Cargo 的一个功能,它允许构建、测试和分享 crate
Crates :
一个模块的树形结构,它形成了库/二进制项目
包(package) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 Cargo.toml 文件,阐述如何去构建这些 crate
模块(Modules)
模块可以将一个 crate 中的代码进行分组,以提高可读性与重用性。
模块可以控制项的私有性,即项是可以/不可以被外部代码使用的(public)
模块可以包含其他项,比如结构体、枚举、常量、trait、函数
执行 cargo new --lib restaurant,来创建一个名为 restaurant 的库
Filename: src/lib.rs
// mod 定义一个模块
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn server_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
可以理解为一个文件系统
模块不仅用于组织代码。还用于私有性边界(privacy boundary):
这条界线不允许外部代码了解、调用和依赖被封装的实现细节。
如果希望创建一个私有函数或结构体,可以将其放入模块。
默认所有项(函数、方法、结构体、枚举、模块和常量)都私有
父模块中的项不能使用子模块中的私有项
子模块中的项可以使用父模块中的项
通过使用 pub 关键字来创建公共项,使子模块的内部部分暴露给上级模块
拿餐馆作比喻,把私有性规则想象成餐馆的后台办公室:
餐馆内的事务对餐厅顾客来说是不可知
办公室经理洞悉其经营的餐厅并在其中做任何事情
路径(path)
路径用于引用模块树中的项
(1)绝对路径(absolute path):从 crate 根部开始,以 crate 名或者字面量 crate 开头。
(2)相对路径(relative path):从当前模块开始,以 self、super 或当前模块的标识符开头
示例
mod front_of_house {
// hosting 公开,如果add_to_waitlist不公开,则仍无法访问
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// eat_at_restaurant 和 front_of_house 位于同一模块,所以可以调用
// eat_at_restaurant 和 front_of_house 是兄弟
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
super
fn serve_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::serve_order(); // 类似于文件系统的..
// 使用 super 进入 back_of_house 父模块,也就是本例中的 crate 根
// 如果这些代码被移动到了其他模块,只需要更新很少的代码
}
fn cook_order() {}
}
结构体
如果在一个结构体定义的前面使用 pub ,这个结构体变成公有
但这个结构体的字段仍是私有。可以根据情况决定每个字段是否公有
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
// back_of_house::Breakfast 具有私有字段
// 所以这个结构体需要提供一个公共的关联函数来构造 Breakfast 的实例
// 如果 Breakfast 没有这样的函数,则无法在eat_at_restaurant中创建meal
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal
// meal.seasonal_fruit = String::from("blueberries");
}
枚举
枚举成员默认就是公有
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() {}
}
}
// 在作用域中增加 use,有点类似于在文件系统增加软链接 symbolic link
// hosting 在作用域中就是有效的名称,如同 hosting 模块被定义于 crate 根一样
// 绝对路径的示例
use crate::front_of_house::hosting;
// 相对路径的示例
// use front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
何为习惯
反例
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// use将函数的父模块引入作用域,必须在调用函数时指定父模块
// 这样可以清晰地表明函数不是在本地定义,同时使完整路径的重复度最小化
// 不然调用函数的地方不知道add_to_waitlist来自哪里
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
add_to_waitlist();
add_to_waitlist();
}
正例
// 结构体和上述相反,使用的是全链路
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
使用父模块将两个具有相同名称的类型引入同一作用域
use std::fmt;
use std::io;
// 两个Result来源不一样
fn function1() -> fmt::Result {
// --snip--
}
fn function2() -> io::Result<()> {
// --snip--
}
要么as提供别名
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
}
fn function2() -> IoResult<()> {
// --snip--
}
pub use 重导
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();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
如果没有指定 pub use,eat_at_restaurant 函数可以在其作用域中调用 hosting::add_to_waitlist,但外部代码则不允许使用这个新路径
外部包
示例:项目使用了一个外部包rand,来生成随机数,提前修改 Cargo.toml
use 起始的包名,它以 rand 包名开头并列出了需要引入作用域的项
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..101);
}
标准库(std)对于其他包来说也是外部 crate
因为标准库随 Rust 语言一同分发,无需修改 Cargo.toml 来引入 std
但需要通过 use 将标准库中定义的项引入项目包的作用域中来引用
比如使用 HashMap:
// 以标准库 crate 名 std 开头的绝对路径
use std::collections::HashMap;
嵌套路径消除 use 行
// 兄弟
use std::{cmp::Ordering, io};
// 父子
use std::io::{self, Write};
glob 运算符将所有的公有定义引入作用域
glob 运算符经常用于测试模块 tests 中
use std::collections::*;