基础篇-包、Crate和模块

  • 木头
  • 更新于 2023-03-01 16:11
  • 阅读 1742

包,库,模块

当你编写大型程序时,组织你的代码显得尤为重要。通过对相关功能进行分组和划分不同功能的代码,你可以清楚在哪里可以找到实现了特定功能的代码,以及在哪里可以改变一个功能的工作方式。

Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能。这有时被称为 “模块系统(the module system)”,包括:

  • (Packages)Cargo 的一个功能,它允许你构建、测试和分享 crate
  • Crate :一个模块的树形结构,它形成了库或二进制项目。
  • 模块(Modules)和use:允许你控制作用域和路径的私有性。
  • 路径(path):一个命名例如结构体、函数或模块等项的方式

模块(Modules)

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_refrigeratorproduce_washing_machine 模块。模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、或者函数。

调用模块路径有两种形式:

  • 绝对路径(absolute path)是以 crate(root)开头的全路径;对于外部 crate的代码,是以 crate名开头的绝对路径,对于对于当前crate 的代码,则以字面值 crate 开头。
  • 相对路径(relative path)从当前模块开始,以 selfsuper 或当前模块的标识符开头。

上面例子无法编译通过:

$ 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();
}

现在代码可以编译通过了。

包和 Crate

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(入口),它编译后是一个可执行文件。

引用外部文件库(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/srcfactory.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/srcmain.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 提供了 selfsuper 两个关键字。

self 在路径中,有两种意思:

  • use self::xxx 表示,加载当前模块中的 xxx。此时 self 可省略;
  • use xxx::{self, yyy},表示,加载当前路径下模块 xxx 本身,以及模块 xxx 下的 yyy

super 表示,当前模块路径的上一级路径,可以理解成父模块。

加载外部库 crate

前面我们讲的,都是在当前crate中的技术。真正我们在开发时,会大量用到外部库(github开源库或者别人写的公开库)。在使用外部库之前先是引入库,在Cargo.tomldependecies段,加上 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
  • 原创
  • 学分: 1
  • 分类: Rust
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
125 订阅 31 篇文章

0 条评论

请先 登录 后评论
木头
木头
0xC020...10cf
江湖只有他的大名,没有他的介绍。