一 、crate
crate称为箱。
crate有两种形式:二进制箱(binary crate)和库箱(library crate)。
二进制箱必须有一个 main 函数,可以编译为可执行程序。
库箱并没有 main 函数,它们也不会编译为可执行程序,它们只是提供一些函数供其他项目使用。
crate root叫箱的根,是一个源文件,Rust编译器以它为起始点,构建你的crate。
二、package
package称为包。包就是一个工程项目,包必须有一个Cargo.toml文件。
一个包至少包含一个箱,这些箱最多包含一个库箱,但可以包含任意数量的二进制箱。
使用cargo new命令创建包。包名就是目录名。
如果使用cargo new proj创建包,src目录下会默认生成一个main.rs源文件。
如果使用cargo new --lib proj命令创建包,src目录下会默认生成一个lib.rs源文件。
Cargo约定:
src/main.rs是一个二进制箱的根。此箱名与包名相同。
src/lib.rs是一个库箱的根。此箱名与包名相同。
如果包只有src/main.rs文件,意味着包只含有一个二进制箱。此箱名与包名相同。
如果一个包同时含有 src/main.rs和src/lib.rs,则它包含两个箱:一个二进制箱和一个库箱,且名字都与包相同。
通过将文件放在src/bin目录下,一个包可以拥有多个二进制箱:src/bin下的每个文件都会编译成一个独立的二进制箱。此目录下的箱名与包名不同,而是与文件名相同。
三、模块
rust模块就是命名空间。
(一)声明模块
使用mod关键字来声明模块。
mod hello {
pub fn say_hello() {
println!("hello world");
}
}
模块内的项默认是private,外部不可见,如果要外部可见需要加pub
模块可以嵌套,形成模块树(module tree)
mod nation {
mod government {
fn govern() {}
}
mod congress {
fn legislate() {}
}
mod court {
fn judicial() {}
}
}
每个箱都是一个模块树。src/main.rs 和 src/lib.rs 叫做箱根,是因为这两个文件为模块树创建了一个名为 crate 的根模块。
crate
└──nation
├──government
│ └──govern
├──congress
│ └──legislate
└──court
└──judicial
(二)使用模块
1.模块的路径
如何在模块树中找到一个项的位置,我们使用路径,就像在文件系统使用路径一样。为了调用一个函数,我们需要知道它的路径。
路径有两种形式:
绝对路径(absolute path)是以箱根开头的全路径:引用外部箱代码,是以箱名开头的绝对路径;引用当前箱代码,则以crate开头。
相对路径(relative path)从当前所在模块开始,以 self、super 或当前模块的标识符开头。
(1)路径以双冒号(::)为分割符
例如:
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();
}
(2)使用以 super 开头的相对路径
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
(3)使用外部箱
必须先添加依赖。在Cargo.toml中添加外部箱所在的包
比如,
[dependencies]
rand = "0.8.5"
Cargo要从 crates.io 下载 rand 和其依赖。
这样就可以使用绝对路径使用外部箱了
use rand::Rng;
fn main() {
let secret_number = rand::thread_rng().gen_range(1..101);
}
(4)std标准库
std也是外部箱。因为标准库随Rust语言一同分发,无需修改 Cargo.toml 来引入 std
比如,
let mut guess = String::new();
std::io::stdin().read_line(&mut guess).expect("failed readline");
2.use语句
无论是使用绝对路径还是相对路径都不便,我们可以使用 use 关键字创建一个短路径。
(1)use关键字将模块标识符引入当前作用域:
实例
mod nation {
pub mod government {
pub fn govern() {}
}
}
use crate::nation::government::govern;
fn main() {
govern();
}
use关键字把govern标识符导入到了当前的模块下,可以直接使用。
(2)可以使用use as为标识符添加别名:
实例
mod nation {
pub mod government {
pub fn govern() {}
}
pub fn govern() {}
}
use crate::nation::government::govern;
use crate::nation::govern as nation_govern;
fn main() {
nation_govern();
govern();
}
这里有两个govern函数,一个是nation下的,一个是government下的,我们用as将nation下的取别名nation_govern。两个名称可以同时使用。
(3)use关键字可以与pub关键字配合使用:
实例
mod nation {
pub mod government {
pub fn govern() {}
}
pub use government::govern;
}
fn main() {
nation::govern();
}
(4)使用大括号引入相同模块的多个子模块,可以显著减少 use 语句的数量
比如,
use std::{cmp::Ordering, io};
(5)使用通配符*引入所有子模块
例子
use std::collections::*;
将 std::collections 中所有公有项引入当前作用域
(三)将模块分割进不同文件
到目前为止,都是在一个文件中定义多个模块。当模块变得更大时,你可能想要将它们移动到单独的文件中。
例如,
文件名: src/lib.rs
mod front_of_house; //声明front_of_house模块,其内容将位于src/front_of_house.rs
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
文件名: src/front_of_house.rs
pub mod hosting {
pub fn add_to_waitlist() {}
}
在src/front_of_house.rs中定义front_of_house模块
在mod front_of_house后使用分号,而不是代码块,表示在其他文件中定义模块。Rust会在与模块同名的文件中查找模块的代码。
继续重构我们例子,将hosting模块也提取到其自己的文件中
文件名: src/front_of_house.rs
pub mod hosting;
创建一个src/front_of_house目录,在src/front_of_house/hosting.rs文件中定义hosting模块:
文件名: src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}