包和 crate 和模块管理
概念
主要包含以下的几个概念:
- 包(Packages): Cargo 的一个功能,它允许你构建、测试和分享 crate。
- Crates :一个模块的树形结构,它形成了库或二进制项目。
- 模块(Modules)和 use: 允许你控制作用域和路径的私有性。
- 路径(path):一个命名例如结构体、函数或模块等项的方式
包和 crate
我们这里直接看官方的解释,看着比较复杂,但实际上 rust 和 cargo 都帮我们做好了。
我们输入命令 cargo new:
$ cargo new my-project
Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
当我们输入了这条命令,Cargo 会给我们的包创建一个 Cargo.toml 文件。查看 Cargo.toml 的内容,会发现并没有提到 src/main.rs,因为 Cargo 遵循的一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。同样的,Cargo 知道如果包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。crate 根文件将由 Cargo 传递给 rustc 来实际构建库或者二进制项目。
在此,我们有了一个只包含 src/main.rs 的包,意味着它只含有一个名为 my-project 的二进制 crate。如果一个包同时含有 src/main.rs 和 src/lib.rs,则它有两个 crate:一个库和一个二进制项,且名字都与包相同。通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制 crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。
模块和路径
这里也直接放代码了,我们用 mod 来声明一个模块,用 pub 来表明这个模块是公开的,比如我们在 src/lib.rs 中写了如下的代码:
#![allow(unused)]
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
pub fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {
// 此处使用了 super 作为相对路径,super 代表了父模块
super::hosting::add_to_waitlist();
}
}
}
fn main() {
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
}
这时候 rust 就会生成如下的模块树
:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
我们可以根据这里面的模块是否公开来使用他们,使用的时候需要注意路径。
路径分为绝对路径和相对路径:
- 绝对路径:即直接根据 crate 来写
crate::front_of_house::hosting::add_to_waitlist()
- 相对路径:相对于同一层级
front_of_house::hosting::add_to_waitlist()
此外我们还可以使用 super 代表父模块从而来表达路径:
// 此处使用了 super 作为相对路径,super 代表了父模块
super::hosting::add_to_waitlist();
使用 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"),
}
}
}
}
pub 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");
}
这里我们通过结构体的函数返回了一个实例,但是由于 season_fruit
没有 pub,所以我们无法改变它的值。
但是枚举类型并不需要这样,枚举类型只需要将枚举前面加上 pub 关键字即可,这样我们直接访问枚举值。
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 的方式导入一个模块,以后我们就可以直接使用这个模块中的数据了。相当于是在 crate 根中增加了一个软链接。
#![allow(unused)]
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
pub fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {
// 此处使用了 super 作为相对路径,super 代表了父模块
super::hosting::add_to_waitlist();
}
}
}
// -------------------------------------------------> use 关键字
use front_of_house::hosting;
fn main() {
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
hosting::add_to_waitlist();
}
}
那我们为什么不直接引入到函数呢,这样直接使用函数不也可以吗?
实际上肯定是可以的,但是我们为了在开发中能够明确自己用的函数、结构体等等是哪一个模块中的,这样写更符合模块化的规范。
此外我们在引入的同时,还可以使用 as
关键字指定别名:
// -------------------------------------------------> use 关键字
use front_of_house::hosting as host;
fn main() {
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
host::add_to_waitlist();
}
}
re-exporting
我们使用 use 之后,在我们编写的代码的作用域中,当然就可以直接使用了,但是当在其他的代码中引入我们编写的代码,这时候还是无法使用的,这时候就需要使用 pub
关键字:
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();
}
这样,在任何代码的作用域中,只要引入了这个模块,就同样可以使用这个模块中引入的其他模块。
嵌套路径
当引入的路径过多是,头部会显得比较臃肿,rust 允许我们在引入的时候进行嵌套:
use std::cmp::Ordering;
use std::io;
// 可以被合并为
use std::{cmp::Ordering, io};
use std::io;
use std::io::Write;
// 可以使用 self 表示自身
use std::io::{self, Write};
引入所有公共定义
use std::collections::*;
模块化
rust 模块化引入的文件位置和文件本身的位置有关,需要按照约定构建目录。
A 类:
B 类: