包,库,模块
当你编写大型程序时,组织你的代码显得尤为重要。通过对相关功能进行分组和划分不同功能的代码,你可以清楚在哪里可以找到实现了特定功能的代码,以及在哪里可以改变一个功能的工作方式。
Rust
有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能。这有时被称为 “模块系统(the module system
)”,包括:
(Packages)
:Cargo
的一个功能,它允许你构建、测试和分享 crate
。Crate
:一个模块的树形结构,它形成了库或二进制项目。(Modules
)和use
:允许你控制作用域和路径的私有性。(path)
:一个命名例如结构体、函数或模块等项的方式Rust
提供了一个关键字 mod
,它可以在一个文件中定义一个模块,或者引用另外一个文件中的模块。
关于模块的一些要点:
crate
中,默认实现了一个隐式的 根模块(root module)
;lower_snake_case
,跟其它的Rust
的标识符一样;Rust
代码;我们还是继续在main.rs
文件创建一个模块:
// 生产车间模块 模块默认是私有的
mod factory {
// 车间有生产冰箱的模块
mod produce_refrigerator {
// 打螺丝 函数
: hit_the_screw() {
println!("螺丝+1");
}
// 电线 函数
fn wire() {
println!("电线+1");
}
}
// 车间有生产洗衣机的模块
mod produce_washing_machine {
// 生产滚筒 函数
fn roller() {
println!("滚筒+1");
}
// 包装 函数
fn packing() {
println!("包装+1");
}
}
}
fn main() {
// 绝对路径 访问模块
crate::factory::produce_refrigerator::hit_the_screw();
// 相对路径 相对路径
factory::produce_refrigerator::hit_the_screw();
}
我们定义一个模块,是以 mod
关键字为起始,然后指定模块的名字(本例中叫做factory
),并且用花括号包围模块的主体。在模块内,我们还可以定义其他的模块,就像本例中的 produce_refrigerator
和 produce_washing_machine
模块。模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、或者函数。
调用模块路径有两种形式:
(absolute path)
是以 crate
根(root)
开头的全路径;对于外部 crate
的代码,是以 crate
名开头的绝对路径,对于对于当前crate
的代码,则以字面值 crate
开头。(relative path)
从当前模块开始,以 self
、super
或当前模块的标识符开头。上面例子无法编译通过:
$ cargo run
Compiling variables v0.1.0 (/projects/variables)
error[E0603]: module `produce_refrigerator` is private
--> src/main.rs:32:21
|
32 | crate::factory::produce_refrigerator::hit_the_screw();
| ^^^^^^^^^^^^^^^^^^^^ private module
|
note: the module `produce_refrigerator` is defined here
--> src/main.rs:4:5
|
4 | mod produce_refrigerator {
| ^^^^^^^^^^^^^^^^^^^^^^^^
error[E0603]: module `produce_refrigerator` is private
--> src/main.rs:35:14
|
35 | factory::produce_refrigerator::hit_the_screw();
| ^^^^^^^^^^^^^^^^^^^^ private module
|
note: the module `produce_refrigerator` is defined here
--> src/main.rs:4:5
|
4 | mod produce_refrigerator {
| ^^^^^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `variables` due to 2 previous errors
上面错误,它告诉我们 produce_refrigerator
模块是私有的。因此我们使用 pub
关键字来标记 produce_refrigerator
模块:
// 生产车间模块
mod factory {
// 车间有生产冰箱的模块
pub mod produce_refrigerator {
// 打螺丝 函数
fn hit_the_screw() {
println!("螺丝+1");
}
// 电线 函数
fn wire() {
println!("电线+1");
}
}
....
不幸的是,代码编译仍然有错误:
$ cargo run
Compiling variables v0.1.0 (/projects/variables)
error[E0603]: function `hit_the_screw` is private
--> src/main.rs:32:43
|
32 | crate::factory::produce_refrigerator::hit_the_screw();
| ^^^^^^^^^^^^^ private function
|
note: the function `hit_the_screw` is defined here
--> src/main.rs:6:9
|
6 | fn hit_the_screw() {
| ^^^^^^^^^^^^^^^^^^
error[E0603]: function `hit_the_screw` is private
--> src/main.rs:35:36
|
35 | factory::produce_refrigerator::hit_the_screw();
| ^^^^^^^^^^^^^ private function
|
note: the function `hit_the_screw` is defined here
--> src/main.rs:6:9
|
6 | fn hit_the_screw() {
| ^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `variables` due to 2 previous errors
在 mod produce_refrigerator
前添加了 pub
关键字,使其变成公有的。伴随着这种变化,如果我们可以访问 factory
,那我们也可以访问 produce_refrigerator
。但是 produce_refrigerator
的 内容(contents)
仍然是私有的;这表明使模块公有并不使其内容也是公有的。模块上的 pub
关键字只允许其父模块引用它,而不允许访问内部代码。因为模块是一个容器,只是将模块变为公有能做的其实并不太多;同时需要更深入地选择将一个或多个项变为公有。
hit_the_screw
函数是私有的。私有性规则不但应用于模块,还应用于结构体、枚举、函数和方法。
让我们继续将pub
关键字放置在 hit_the_screw
函数的定义之前,使其变成公有:
// 生产车间模块
mod factory {
// 车间有生产冰箱的模块
pub mod produce_refrigerator {
// 打螺丝 函数
pub fn hit_the_screw() {
println!("螺丝+1");
}
// 电线 函数
pub fn wire() {
println!("电线+1");
}
}
...
}
fn main() {
// 绝对路径 访问模块
crate::factory::produce_refrigerator::hit_the_screw();
// 相对路径 相对路径
factory::produce_refrigerator::hit_the_screw();
}
现在代码可以编译通过了。
Rust
中,crate
是一个独立的可编译单元。具体说来,就是一个或一批文件(如果是一批文件,那么有一个文件是这个 crate
的入口)。它编译后,会对应着生成一个可执行文件或一个库。库
并没有main
函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西,使其他项目也能使用这些东西。
执行 cargo new --lib foo
,会得到如下目录层级:
foo
├── Cargo.toml
└── src
└── lib.rs
这里,lib.rs
就是一个 crate
(入口),它编译后是一个库
。一个工程下可以包含不止一个crate
。
执行 cargo new bar
,会得到如下目录层级:
bar
├── Cargo.toml
└── src
└── main.rs
这里,main.rs
就是一个 crate
(入口),它编译后是一个可执行文件。
模块全部写在一个文件里面这并不是我们想看到的,接下来我们把上面的生产车间模块单独成一个库文件,新建一个项目cargo new learn_crate
:
learn_crate
├── Cargo.toml
└── src
└── main.rs
cd learn_crate
在项目内创建库cargo new --lib mylib
:
learn_crate
├── Cargo.toml
├── mylib
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── src
└── main.rs
完整命令:
$ cargo new learn_crate
$ cd learn_crate
$ cargo new --lib mylib
在learn_crate/mylib/src
新factory.rs
文件:
learn_crate
├── Cargo.lock
├── Cargo.toml
├── mylib
│ ├── Cargo.toml
│ └── src
│ ├── factory.rs
│ └── lib.rs
├── src
└── main.rs
编辑factory.rs
文件把之前的代码复制过来:
// 车间有生产冰箱的模块
pub mod produce_refrigerator {
// 打螺丝 函数
pub fn hit_the_screw() {
println!("螺丝+1");
}
// 电线 函数
pub fn wire() {
println!("电线+1");
}
}
// 车间有生产洗衣机的模块
pub mod produce_washing_machine {
// 生产滚筒 函数
pub fn roller() {
println!("滚筒+1");
}
// 包装 函数
pub fn packing() {
println!("包装+1");
}
}
编辑`learn_crate/mylib/src
下面的lib.rs
文件导出模块:
pub mod factory;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
接下来我们就可以在learn_crate/src
的main.rs
使用,在使用之前我们需要先配置根目录下的Cargo.toml
文件,在dependencies
下导入我们的要使用的库:
[dependencies]
# 库名称 库路径
mylib = {path = "./mylib/"}
现在就可以去main.rs
使用该库:
use mylib::factory::{produce_refrigerator, produce_washing_machine};
fn main() {
// 绝对路径
mylib::factory::produce_refrigerator::hit_the_screw();
mylib::factory::produce_washing_machine::packing();
// 使用use调用
produce_refrigerator::hit_the_screw();
produce_washing_machine::packing();
}
运行看看结果:
螺丝+1
包装+1
螺丝+1
包装+1
引入的时候,可以通过 as
关键字重命名:
use mylib::factory::{produce_refrigerator as a, produce_washing_machine as b};
fn main() {
// 使用重命名调用
a::hit_the_screw();
b::packing();
}
也可以引入全部模块:
use mylib::factory::*;
fn main() {
produce_refrigerator::hit_the_screw();
produce_washing_machine::packing();
}
我们还可以使用 pub
来设计公有的结构体和枚举,不过关于在结构体和枚举上使用 pub
还有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了pub
,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有:
mod mod_a {
#[derive(Debug)]
pub struct A {
pub number: i32,
name: String,
}
impl A {
pub fn new_a() -> Self {
A {
number: 1,
name: String::from("AAA"),
}
}
pub fn print_a(&self) {
// name可以访问
println!("number: {},name:{}", self.number, self.name);
}
}
}
use mod_a::A;
fn main() {
let a = A::new_a();
let number = a.number;
let name = a.name; // name是私有的无法访问
}
因为 A
结构体的number
字段是公有的,所以我们可以在 main
中使用点号来随意的读写 number
字段。注意,我们不能在 main
中使用 name
字段,因为name
是私有的。
通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式:
mod mod_a {
pub mod mod_b {
pub fn print_b() {
println!("mod b")
}
pub mod mod_d {
pub fn print_d() {
super::print_b(); // 调用父模块函数
println!("mod d")
}
}
}
pub mod mod_c {
pub fn print_c1() {
println!("mod c1")
}
pub fn print_c2() {
self::print_c1(); // 调用当前模块函数
print_c1(); // self实际可以省略
println!("mod c2")
}
}
}
fn main() {
mod_a::mod_c::print_c2();
mod_a::mod_b::mod_d::print_d();
}
与文件系统概念类似,模块路径也有相对路径和绝对路径的概念。为此,Rust
提供了 self
和 super
两个关键字。
self
在路径中,有两种意思:
xxx
。此时 self
可省略;xxx
本身,以及模块 xxx
下的 yyy
;super
表示,当前模块路径的上一级路径,可以理解成父模块。
前面我们讲的,都是在当前crate
中的技术。真正我们在开发时,会大量用到外部库(github开源库或者别人写的公开库)。在使用外部库之前先是引入库,在Cargo.toml
的 dependecies
段,加上 xxx="version num"
,本次使用rust
提供的加密库:
[dependencies]
rust-crypto = "0.2"
使用外部库:
extern crate crypto;
use crypto::digest::Digest;
use crypto::sha3::Sha3;
fn main() {
let mut hasher = Sha3::sha3_256();
// 输入加密字符串
hasher.input_str("hello world");
// 输出加密以后的字符串
let result = hasher.result_str();
println!("{}", result);
}
运行项目,第一次使用导入外部库会先下载库:
644bcc7e564373040999aac89e7622f3ca71fba1d972fd94a31c3bfbf24e3938
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!