包和模块
Rust 提供了模块系统,可以将代码按层次分成多个逻辑单元(模块),并管理这些模块之间的可见性(公有或私有)。
模块是项(item)的集合,项可以是:函数,结构体,trait,impl
块,甚至其它模块。
Crate 和 package
crate 是 Rust 最小的编译单元,它的形式包括二进制项和库。二进制项可以被编译为可执行程序,比如一个命令行程序或者一个服务器。它们必须有一个 main 函数来定义当程序被执行的时候所需要做的事情。库并没有 main 函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西,使其他项目也能使用这些东西。
包(package) Cargo 就是一个包含构建Rust代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。包中可以包含至多一个库 crate。包中可以包含任意多个二进制 crate,但是必须至少包含一个 crate(无论是库的还是二进制的)。
Package vs Crate:
1.crate 是 Rust 最小的编译单元;而Package 包含一系列功能的一个或者多个 crate。 2.crate 有两种形式:二进制项(binary)和库(library);Package 会包含一个 Cargo.toml 文件,阐述如何去构建这些 crate。 3.crate 面向开发者组织代码;Package 面向 Cargo 组织依赖。
伴随着项目的增长,应该通过将代码分解为多个模块和多个文件来组织代码,伴随着package
(包)的增长,你可以将package
(包)中的部分代码提取出来,做成独立的 crate,这些 crate 则作为外部依赖项。
通过Rust包管理工具Cargo可以创建二进制包:
$ cargo new hello_bin
Created binary (application) `hello_bin` package
也可以创建库包:
$ cargo new hello_lib
Created binary (application) `hello_lib` package
上面创建的 Package 中仅包含 src/main.rs
或者 src/lib.rs
,意味着它仅包含一个二进制同名包 hello_bin
或者库同名包 hello_lib
。如果一个 Package 同时拥有 src/main.rs
和 src/lib.rs
,那就意味着它包含两个包:库包和二进制包,这两个包名也都是 Package 同名。
一个真实项目中典型的 Package,会包含多个二进制包,这些包文件被放在 src/bin
目录下,每一个文件都是独立的二进制包,同时也会包含一个库包,该包只能存在一个 src/lib.rs
:
rust
.
├── Cargo.toml // 包的配置文件
├── src
│ ├── main.rs // 包的主函数
│ ├── lib.rs // 库包
│ └── bin // 可以放置多个二进制包
│ └── main1.rs
│ └── main2.rs
├── tests // 集成测试
│ └── some_integration_tests.rs
├── benches // 基准测试
│ └── simple_bench.rs
└── examples // 代码示例
└── simple_example.rs
模块及其作用域和可见性
模块
模块让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。因为一个模块中的代码默认是私有的,所以还可以利用模块控制项的私有性。私有项是不可为外部使用的内在详细实现。我们也可以将模块和它其中的项标记为公开的,这样,外部代码就可以使用并依赖与它们。
首先讲一下在 Rust 中创建模块的语法:
rust
mod foo; // 定义名为foo的模块
mod foo {
// foo模块的代码
fn func_1() {}
mod bar_1 {
// 在模块foo中嵌套了一个模块bar_1
fn func_2() {}
}
mod bar_2 {
// 在模块foo中嵌套了一个模块bar_2
fn func_3() {}
}
}
注意:
- 使用 mod 关键字来创建新模块,后面紧跟着模块名称;
- 模块中可以定义各种 Rust 类型,例如函数、结构体、枚举、trait等;
- foo是bar_1和bar_2的父模块,bar_1和bar_2是foo的子模块;
模块的作用域和可见性
想要调用一个函数,就需要知道它的路径,在 Rust 中,这种路径有两种形式:
- 绝对路径,从包根开始,路径名以包名或者 crate 作为开头
- 相对路径,从当前模块开始,以 self,super 或当前模块的标识符作为开头
绝对路径形式,在上面的模块中调用 func_2() 可以采用下面的方式:
rust
use crate::foo::bar_1::func_2();
相对路径的方式,在上面的模块bar_2中调用 func_2() 可以采用下面的方式:
rust
use bar_2::func_2();
如果采用路径的方式调用另外一个模块的函数,如下代码所示例子会报错:
rust
// 错误示例
mod foo {
mod bar {
fn func_1() {}
}
pub fn func_2() {}
}
fn main() {
fn func_3() {
// 绝对路径
crate::foo::bar::func_1();
// 相对路径
foo::bar::func_1();
// 绝对路径
crate::foo::func_2();
// 相对路径
foo::func_2();
}
}
因为模块 bar 默认是私有的,模块外部无法进行访问调用。但是 foo 模块内部的函数可以调用,这是因为 foo 和 func_3() 同属于一个包根作用域内,同一个模块内的代码自然不存在私有化问题。
但是 bar 模块是私有的,外部模块和函数都不能进行调用,func_2 函数能进行调用是因为它声明了 pub,所以外部可见,可以调用。模块的可见性并不代表模块内部项的可见性,模块的可见性仅仅是允许其它模块去引用它,但是想要引用它内部的项,还得继续将对应的项标记为 pub。
Rust 在默认情况下,所有的类型都是私有化的,包括函数、方法、结构体、枚举、常量、模块。在 Rust 中,父模块完全无法访问子模块中的私有项,但是子模块却可以访问父模块及其之上的模块的私有项。
在相对路径的调用中,除了以上的方式,还可以采用 self 和 super 的方式进行。例如:
rust
mod foo {
mod bar {
pub fn func_1() {}
}
fn func_2() {
self::bar::func_1(); // self 表示在自身的模块中寻找模块 bar 进行调用
}
mod xyz {
fn func_3() {
super::bar::func_1(); // super表示从上一级的父模块开始进行调用
}
}
}
参考链接:
https://doc.rust-lang.org/rust-by-example/mod/visibility.html
https://doc.rust-lang.org/stable/book/ch07-02-defining-modules-to-control-scope-and-privacy.html
引用模块项目的路径
如果代码中,每次调用都采用绝对路径或者相对路径的方式进行的话,那么看起来会很复杂和臃肿。因此我们需要一个办法来简化这种使用方式,在 Rust中,可以使用 use 关键字把路径提前引入到当前作用域中,随后的调用就可以省略该路径,极大地简化了代码。
使用use引入模块
在下面的例子中,将 bar 模块通过 use 的方式引入,这样后面进行调用的时候就不用把全部路径写出来:
rust
mod foo {
pub mod bar {
pub fn func_1() {}
}
}
fn main() {
use crate::foo::bar; // 通过use引入模块
fn func_3() {
bar::func_1(); // 再通过 :: 的方式进行调用,当调用次数很多的时候相比于上面的方式减少了复杂性
}
}
或者也可以直接引入模块中的某一个特定函数:
rust
mod foo {
pub mod bar {
pub fn func_1() {}
}
}
fn main() {
use crate::foo::bar::func_1; // 引入模块中的特定函数
fn func_3() {
func_1();
}
}
如果在不同的模块中存在同名的项(比如同名函数)可以采用不同的父模块引入来进行调用,例如:
rust
mod foo {
pub fn example() {}
}
mod bar {
pub fn example() {}
}
fn main() {
use crate::foo;
use crate::bar;
foo::example();
bar::example();
}
或者可以采用 as关键字对同名的函数进行别名处理,例如:
rust
mod foo {
pub fn example() {}
}
mod bar {
pub fn example() {}
}
fn main() {
use crate::foo::example as ex_1;
use crate::bar::example as ex_2;
ex_1();
ex_2();
}
使用标准库和第三方库
Rust 标准库提供大量的模块和方法可以使用,利用use将模块或者函数引入,例如:
rust
#![allow(unused)]
use std::io; // 引入标准库中的io模块
use std::io::prelude::*; // *的意思是将prelude下所有的项全部引入
use std::fs::File; // 引入标准库中的fs::File模块
fn main() -> io::Result<()> {
let mut f = File::open("foo.txt")?;
let mut buffer = [0; 10];
// read up to 10 bytes
let n = f.read(&mut buffer)?;
println!("The bytes: {:?}", &buffer[..n]);
Ok(())
}
Rust 生态中提供了很多高质量的第三方包,你可以通过在crates.io上搜索,获得自己想要的第三方库。 三方库的使用需要在 Cargo.toml 中[dependencies]添加依赖项,例如添加:
rand = "0.8.3"
在使用时,使用use引入即可:
rust
use rand::Rng; // 引入rand包的Rng模块
fn main() {
let secret_number = rand::thread_rng().gen_range(1..101);
}
如果要引入的模块的项较多,可以使用 {} 将多个引入项包括在里边,这样可以简化引入代码。例如:
rust
use std::collections::HashMap;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::cmp::Ordering;
use std::time;
use std::io;
use std::io::Write;
可以转换为:
rust
use std::collections::{HashMap,BTreeMap,HashSet};
use std::{cmp::Ordering, time};
use std::io::{self, Write};
参考链接:https://doc.rust-lang.org/stable/book/ch07-03-paths-for-referring-to-an-item-in-the-module-tree.html