【Rust】Rust学习 第七章使用包、Crate和模块管理不断增长的项目

news2024/12/26 11:43:05

目前为止,我们编写的程序都在一个文件的一个模块中。伴随着项目的增长,你可以通过将代码分解为多个模块和多个文件来组织代码。一个包可以包含多个二进制 crate 项和一个可选的 crate 库。伴随着包的增长,你可以将包中的部分代码提取出来,做成独立的 crate,这些 crate 则作为外部依赖项。

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

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

7.1 包和crate

模块系统的第一部分,我们将介绍包和 crate。crate 是一个二进制项或者库。crate root 是一个源文件,Rust 编译器以它为起始点,并构成你的 crate 的根模块。package) 是提供一系列功能的一个或者多个 crate。一个包会包含有一个 Cargo.toml 文件,阐述如何去构建这些 crate。

一个包中至多 只能 包含一个库 crate(library crate);包中可以包含任意多个二进制 crate(binary crate);包中至少包含一个 crate,无论是库的还是二进制的。

emm......这话说的

一个包至少包含一个crate,其中,可以包含任意个二进制crate、但最多只能包含一个库crate

看看创建包的时候会发生什么

具体的文件夹是这样的

当我们输入了这条命令,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 来实际构建库或者二进制项目。

一个包至少包含一个crate,其中可以包含任意个二进制crate、最多只能包含一个库crate

src/main.rs是一个与包同名的二进制crate(也是二进制的根crate);

如果该目录下还存在src/lib.rs,则该包下还有一个同名的库crate(且src/lib.rs的crate库)

注:这里lib.rs表示库文件。满足上述条件【一个包至少包含一个crate,其中可以包含任意个二进制crate、最多只能包含一个库crate

在此,我们有了一个只包含 src/main.rs 的包,意味着它只含有一个名为test05的二进制 crate。如果一个包同时含有 src/main.rs 和 src/lib.rs,则它有两个 crate:一个库和一个二进制项,且名字都与包相同。通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制 crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。

很绕

一个 crate 会将一个作用域内的相关功能分组到一起,使得该功能可以很方便地在多个项目之间共享。举一个例子,在第二章使用的 rand crate 提供了生成随机数的功能。通过将 rand crate 加入到我们项目的作用域中,我们就可以在自己的项目中使用该功能。rand crate 提供的所有功能都可以通过该 crate 的名字:rand 进行访问。

将一个 crate 的功能保持在其自身的作用域中,可以知晓一些特定的功能是在我们的 crate 中定义的还是在 rand crate 中定义的,这可以防止潜在的冲突。

7.2 定义模块来控制作用域与私有性

模块 让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。模块还可以控制项的 私有性,即项是可以被外部代码使用的(public),还是作为一个内部实现的内容,不能被外部代码使用(private)。

案例:在餐饮业,餐馆中会有一些地方被称之为 前台front of house),还有另外一些地方被称之为 后台back of house)。前台是招待顾客的地方,在这里,店主可以为顾客安排座位,服务员接受顾客下单和付款,调酒师会制作饮品。后台则是由厨师工作的厨房,洗碗工的工作地点,以及经理做行政工作的地方组成。

我们可以将函数放置到嵌套的模块中,来使我们的 crate 结构与实际的餐厅结构相同。通过执行 cargo new --lib restaurant,来创建一个新的名为 restaurant 的库。

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
        fn seat_at_table() {}
    }

    mod serving {
        fn take_order() {}
        fn server_order() {}
        fn take_payment() {}
    }
}

定义一个模块,是以 mod 关键字为起始,然后指定模块的名字(本例中叫做 front_of_house),并且用花括号包围模块的主体。在模块内,我们还可以定义其他的模块,就像本例中的 hosting 和 serving 模块。模块还可以保存一些定义的其他项,比如结构体、枚举、常量、特性、或者函数。

src/main.rs 和 src/lib.rs 叫做 crate 根。之所以这样叫它们的原因是,这两个文件的内容都是一个从名为 crate 的模块作为根的 crate 模块结构,称为 模块树module tree)。

模块树的结构

crate
 └── front_of_house
     ├── hosting
     │   ├── add_to_waitlist
     │   └── seat_at_table
     └── serving
         ├── take_order
         ├── serve_order
         └── take_payment

这个树展示了一些模块是如何被嵌入到另一个模块的(例如,hosting 嵌套在 front_of_house 中)。这个树还展示了一些模块是互为 兄弟siblings) 的,这意味着它们定义在同一模块中(hosting 和 serving 被一起定义在 front_of_house 中)。继续沿用家庭关系的比喻,如果一个模块 A 被包含在模块 B 中,我们将模块 A 称为模块 B 的 child),模块 B 则是模块 A 的 parent)。注意,整个模块树都植根于名为 crate 的隐式模块下。

7.3 路径用于引用模块树中的项

路径有两种形式:

  • 绝对路径absolute path)从 crate 根开始,以 crate 名或者字面值 crate 开头。
  • 相对路径relative path)从当前模块开始,以 selfsuper 或当前模块的标识符开头。

绝对路径和相对路径都后跟一个或多个由双冒号(::)分割的标识符。

如何调用 add_to_waitlist 函数?还是同样的问题,add_to_waitlist 函数的路径是什么?

修改一下src/lib.rs(这个编译不成功)

mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}

eat_at_restaurant 函数是我们 crate 库的一个公共API,所以使用 pub 关键字来标记它。

第一种方式,我们在 eat_at_restaurant 中调用 add_to_waitlist 函数,使用的是绝对路径。add_to_waitlist 函数与 eat_at_restaurant 被定义在同一 crate 中,这意味着我们可以使用 crate 关键字为起始的绝对路径。

第二种方式,我们在 eat_at_restaurant 中调用 add_to_waitlist,使用的是相对路径。这个路径以 front_of_house 为起始,这个模块在模块树中,与 eat_at_restaurant 定义在同一层级。与之等价的文件系统路径就是 front_of_house/hosting/add_to_waitlist。以名称为起始,意味着该路径是相对路径。

编译会报错

错误信息说 hosting 模块是私有的。换句话说,我们拥有 hosting 模块和 add_to_waitlist 函数的的正确路径,但是 Rust 不让我们使用,因为它不能访问私有片段。

使用pub关键字暴露路径

给上述代码加上pub,依旧报错,还需给函数也加上,私有性规则不但应用于模块,还应用于结构体、枚举、函数和方法。

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}

在绝对路径,我们从 crate,也就是 crate 根开始。然后 crate 根中定义了 front_of_house 模块。front_of_house 模块不是公有的,不过因为 eat_at_restaurant 函数与 front_of_house 定义于同一模块中(即,eat_at_restaurant 和 front_of_house 是兄弟),我们可以从 eat_at_restaurant 中引用 front_of_house。接下来是使用 pub 标记的 hosting 模块。我们可以访问 hosting 的父模块,所以可以访问 hosting。最后,add_to_waitlist 函数被标记为 pub ,我们可以访问其父模块,所以这个函数调用是有效的!

在相对路径,其逻辑与绝对路径相同,除了第一步:不同于从 crate 根开始,路径从 front_of_house 开始。front_of_house 模块与 eat_at_restaurant 定义于同一模块,所以从 eat_at_restaurant 中开始定义的该模块相对路径是有效的。接下来因为 hosting 和 add_to_waitlist 被标记为 pub,路径其余的部分也是有效的,因此函数调用也是有效的!

使用 super 起始的相对路径

还可以使用 super 开头来构建从父模块开始的相对路径。这么做类似于文件系统中以 .. 开头的语法。

下面代码模拟了厨师更正了一个错误订单,并亲自将其提供给客户的情况。

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();
}


// 加了这段
fn serve_order() {}
mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::serve_order();
    }
    fn cook_order() {}
}

fix_incorrect_order 函数在 back_of_house 模块中,所以我们可以使用 super 进入 back_of_house 父模块,也就是本例中的 crate 根。在这里,我们可以找到 serve_order。成功!我们认为 back_of_house 模块和 server_order 函数之间可能具有某种关联关系,并且,如果我们要重新组织这个 crate 的模块树,需要一起移动它们。

创建公有的结构体和枚举

还可以使用 pub 来设计公有的结构体和枚举,不过有一些额外的细节需要注意。如果在一个结构体定义的前面使用了 pub ,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。可以根据情况决定每个字段是否公有。

与之相反,如果将枚举设为公有,则它的所有成员都将变为公有。只需要在 enum 关键字前面加上 pub

7.4 使用use关键字将名称引入作用域

导入头文件???

到目前为止,似乎编写的用于调用函数的路径都很冗长且重复,并不方便。无论我们选择 add_to_waitlist 函数的绝对路径还是相对路径,每次想要调用 add_to_waitlist 时,都必须指定front_of_house 和 hosting。幸运的是,有一种方法可以简化这个过程。可以一次性将路径引入作用域,然后使用 use 关键字调用该路径中的项,就如同它们是本地项一样。

mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
    // Absolute path
    hosting::add_to_waitlist();

    // Relative path
    hosting::add_to_waitlist();
}

通过 use 引入作用域的路径也会检查私有性,同其它路径一样。

创建惯用的use路径

要想使用 use 将函数的父模块引入作用域,必须在调用函数时指定父模块,这样可以清晰地表明函数不是在本地定义的,同时使完整路径的重复度最小化。

另一方面,使用 use 引入结构体、枚举和其他项时,习惯是指定它们的完整路径。

使用as关键字提供新的名称

使用 use 将两个同名类型引入同一作用域这个问题还有另一个解决办法:在这个类型的路径后面,我们使用 as 指定一个新的本地名称或者别名

#![allow(unused_variables)]
fn main() {
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}
}

使用外部包

crates.io 上有很多 Rust 社区成员发布的包,将其引入你自己的项目都需要一道相同的步骤:在 Cargo.toml 列出它们并通过 use 将其中定义的项引入项目包的作用域中。

注意标准库(std)对于你的包来说也是外部 crate。因为标准库随 Rust 语言一同分发,无需修改 Cargo.toml 来引入 std,不过需要通过 use 将标准库中定义的项引入项目包的作用域中来引用它们,比如我们使用的 HashMap

#![allow(unused_variables)]
fn main() {
use std::collections::HashMap;
}

嵌套路径来消除大量的 use 行

use std::cmp::Ordering;
use std::io;

可以写成

use std::{cmp::Ordering, io};

在较大的程序中,使用嵌套路径从相同包或模块中引入很多项,可以显著减少所需的独立 use 语句的数量!

use std::io;
use std::io::Write;

可以写成

use std::io::{self, Write};

通过 glob 运算符将所有的公有定义引入作用域

如果希望将一个路径下 所有 公有项引入作用域,可以指定路径后跟 *,glob 运算符:

use std::collections::*;

这个 use 语句将 std::collections 中定义的所有公有项引入当前作用域。使用 glob 运算符时请多加小心!Glob 会使得我们难以推导作用域中有什么名称和它们是在何处定义的。

7.5 将模块分割进不同文件

上述案例中,将 front_of_house 模块移动到属于它自己的文件 src/front_of_house.rs 中,通过改变 crate 根文件。crate 根文件是 src/lib.rs,这也同样适用于以 src/main.rs 为 crate 根文件的二进制 crate 项。

文件名: src/lib.rs

mod front_of_house;

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
    hosting::add_to_waitlist();
}

声明 front_of_house 模块,其内容位于 src/front_of_house.rs

pub mod hosting {
    pub fn add_to_waitlist() {}
}

接着我们创建一个 src/front_of_house 目录和一个包含 hosting 模块定义的 src/front_of_house/hosting.rs 文件:

pub fn add_to_waitlist() {}

模块树依然保持相同,eat_at_restaurant 中的函数调用也无需修改继续保持有效,即便其定义存在于不同的文件中。这个技巧让你可以在模块代码增长时,将它们移动到新文件中。

注意,src/lib.rs 中的 pub use crate::front_of_house::hosting 语句是没有改变的,在文件作为 crate 的一部分而编译时,use 不会有任何影响。mod 关键字声明了模块,Rust 会在与模块同名的文件中查找模块的代码。

总结

Rust 提供了将包组织进 crate、将 crate 组织进模块,和通过指定绝对或相对路径从一个模块引用另一个模块中定义的项的方式。你可以通过使用 use 语句将路径引入作用域,这样在多次使用时可以使用更短的路径。模块定义的代码默认是私有的,不过可以选择增加 pub 关键字使其定义变为公有。

参考:使用包、Crate 和模块管理不断增长的项目 - Rust 程序设计语言 简体中文版 (bootcss.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/853388.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ArcGIS Pro实践技术应用暨基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例应用

GIS是利用电子计算机及其外部设备&#xff0c;采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲&#xff0c;它是在一定的地域内&#xff0c;将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来&#xff0c;达到对地理和属性信息的综合管理。GIS的…

动力节点新版Docker实用教程,从入门到高阶一套搞定

Docker是一种轻量级的容器化平台&#xff0c;它可以大大简化应用程序的在不同环境中的部署、管理与扩展。 学习Docker可以让你更好地了解和应用容器化技术&#xff0c;实现软件运环境的快速部署和管理。 如何系统全面掌握Docker&#xff1f; 推荐一套天花板级Docker教程&#x…

前端页面--视觉差效果

代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><link rel"stylesheet" href"https://un…

Explorable Tone Mapping Operators

Abstract 色调映射在高动态范围(HDR)成像中起着至关重要的作用。 它的目的是在有限动态范围的介质中保存HDR图像的视觉信息。 虽然许多工作已经提出从HDR图像中提供色调映射结果&#xff0c;但大多数只能以一种预先设计的方式进行色调映射。 然而&#xff0c;声调映射质量的主…

lc1.两数之和

暴力解法&#xff1a;两个for循环&#xff0c;寻找和为target的两个数的索引 时间复杂度&#xff1a;O(n2) 空间复杂度&#xff1a;O(1) 哈希表&#xff1a;遍历数组&#xff0c;将nums数组的数和索引分别存储在map的key和value中&#xff0c;一边遍历&#xff0c;一边寻找是…

多语言多用户购物网站--海外仓进出口贸易平台开发

搭建一个多语言多用户购物网站需要具备一定的技术和资源&#xff0c;下面是一个大致的步骤&#xff1a; 1.确定需求&#xff1a;首先确定购物网站的功能需求&#xff0c;包括商品展示、购物车、订单管理、支付方式、物流管理等。同时还需要考虑到海外仓进出口贸易的特点&#…

如何使用Pycharm 快速搭建 Django 项目 (分享详细图文教程)

1. 准备工作 在开始创建Django项目之前&#xff0c;需要先确保已经安装了Python和Pycharm。并且python中已经安装好了Django依赖。 1安装python&#xff08;这里我安装使用的是python3.11.4稳定版本&#xff09; 官网下载太慢了这里直接贴网盘下载连接了&#xff0c;一起贴出py…

java版工程项目管理系统源码+系统管理+系统设置+项目管理+合同管理+二次开发em

​ 鸿鹄工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离构建工程项目管理系统 1. 项目背景 一、随着公司的快速发展&#xff0c;企业人员和经营规模不断壮大。为了提高工程管理效率、减轻劳动强度、提高信息处理速度和准确性&#xff0c;公司对内部…

解决vue3中不能使用vue-simple-uploader

vue-simple-uploader本身是基于vue2的&#xff0c;直接npm i vue-simple-uploader -S下载下来版本的是0.7.6。在vue3中无法使用会报错。 解决&#xff1a;使用next安装接下来要发布的版本就会下载1.0.1版本&#xff0c;即可使用vue3 npm i vue-simple-uploadernext -S 注意&…

基于Echarts的大数据可视化模板:智慧物流管理

目录 引言物流管理的重要性大数据可视化在解决物流管理挑战中的作用智慧物流概述定义智慧物流的概念和特点智慧物流的关键技术和平台风险管理和预测:交通拥堵情况和风险预警Echarts与大数据可视化Echarts库以及其在大数据可视化领域的应用优势开发过程和所选设计方案模板如何满…

企业计算机服务器中了locked勒索病毒怎么办,如何预防勒索病毒攻击

计算机服务器是企业的关键信息基础设备&#xff0c;随着计算机技术的不断发展&#xff0c;企业的计算机服务器也成为了众多勒索者的攻击目标&#xff0c;勒索病毒成为当下计算机服务器的主要攻击目标。近期&#xff0c;我们收到很多企业的求助&#xff0c;企业的服务器被locked…

linux程序保护机制gcc编译选项

预备知识&#xff1a; 计算机内存的结构通常包括以下几个主要部分&#xff1a; 1.代码段(Code Segment)&#xff1a;也称为文本段&#xff0c;存储程序的可执行指令。代码段是被标记为可执行的&#xff0c;程序从代码段中获取指令并执行。 2.数据段(Data Segment)&#xff1a…

聚焦智慧医疗-RK3566智能主板助力POCT领域

POCT又称即时检验&#xff0c;是IVD(体外诊断)行业的一个细分领域&#xff0c;是指在采样现场即刻进行的快速诊断&#xff0c;省去了标本在实验室检验的复杂处&#xff0c;利用便携式分析仪器及配套试剂快速得到检测结果的一种检测方式。常见的POCT设备有生化分析仪、免疫分析仪…

Netty的ByteToMessageDecoder分析

说明 io.netty.handler.codec.ByteToMessageDecoder是一个解码器&#xff0c;从字节数据转换为其它类型的数据。ByteToMessageDecoder内部有一个累加器&#xff0c;将收到的字节读出来累积到一个ByteBuf中。ByteToMessageDecoder是个抽象类型&#xff0c;其中抽象函数decode(C…

Adobe全家桶-Prelude 2022安装教程

软件介绍 Adobe Prelude简称Pl&#xff0c;是Adobe出品的一款专业级别的视频编辑软件。Adobe Prelude软件将卓越的性能、优美的改进用户界面和许多奇妙的创意功能结合在一起&#xff0c;包括用于动态时间轴裁切、扩展的多机编辑、调整图层等。可用于结构化或简化视频制作工作流…

侯捷 C++面向对象编程笔记——8 静态 模板 namespace

8.1 static 对于非静态的函数和数据&#xff1a; 非静态的成员函数通过this指针来处理不同的数据&#xff08;一份函数—>多个对象&#xff09; 对于静态的函数和数据&#xff1a; 静态函数没有this&#xff0c;不能处理一般的数据&#xff0c;只能处理静态的数据 例1&a…

webshell免杀项目-Webshell_Generate(四)

用于生成各类免杀webshell 该工具没什么技术含量&#xff0c;学了一点javafx&#xff0c;使用jdk8开发出了几个简单功能用来管理webshell。页面比较low。 工具整合并改写了各类webshell&#xff0c;支持各个语言的cmd、蚁剑、冰蝎、哥斯拉&#xff0c;又添加了实际中应用到的一…

【Opencv入门到项目实战】(七):图像轮廓检测

所有订阅专栏的同学可以私信博主获取源码文件 文章目录 引言1.​轮廓检测2.轮廓特征3.轮廓近似4.外接矩形总结 引言 这一篇文章我们来讨论图像轮廓相关的知识点&#xff0c;什么叫做轮廓&#xff0c;从定义上来说它是指图像中连续的曲线或边界&#xff0c;表示了图像中目标的…

h5高德地图定位与Audio标签冲突

看到此文说明你也遇见此问题了&#xff0c;在ios设备使用(高德地图、百度地图)定位&#xff0c;如果在页面中有audio标签则会出现定位失败要么就是音频无法播放&#xff0c;此问题已和官方证实 是冲突问题&#xff0c;暂无解决方案。 因为高德地图也是基于原生定位开发的&#…

51单片机学习--红外遥控(外部中断)

需要利用下面这个红外接收头&#xff0c;OUT口会发出红外信号对应的高低电平&#xff0c;由于发送的速度很快&#xff0c;所以需要把OUT引脚接在外部中断引脚上&#xff0c;当OUT一旦产生下降沿&#xff0c;马上进中断&#xff0c;这样响应会更及时。 外部中断引脚位于P3_2和P…