使用包、Crate 和模块管理项目(上)
目录
1、包和Crate
2、定义模块来控制作用域与私有性
2.1 在模块中对相关代码进行分组
3、引用模块项目的路径
3.1 使用 pub 关键字暴露路径
二进制和库 crate 包的最佳实践
3.2 super 开始的相对路径
3.3 创建公有的结构体和枚举
Rust 有许多功能可以让你管理代码的组织,包括哪些内容可以被公开,哪些内容作为私有部分,以及程序每个作用域中的名字。这些功能,有时被统称为 “模块系统(the module system)”,包括:
- 包(Packages):Cargo 的一个功能,它允许你构建、测试和分享 crate。
- Crates :一个模块的树形结构,它形成了库或二进制项目。
- 模块(Modules)和 use:允许你控制作用域和路径的私有性。
- 路径(path):一个命名例如结构体、函数或模块等项的方式
1、包和Crate
crate 是 Rust 在编译时最小的代码单位。如果你用 rustc
而不是 cargo
来编译一个文件,编译器还是会将那个文件认作一个 crate。crate 可以包含模块,模块可以定义在其他文件,然后和 crate 一起编译,我们会在接下来的章节中遇到。
crate 有两种形式:二进制项和库。二进制项 可以被编译为可执行程序,比如一个命令行程序或者一个服务器。它们必须有一个 main
函数来定义当程序被执行的时候所需要做的事情。目前我们所创建的 crate 都是二进制项。
库 并没有 main
函数,它们也不会编译为可执行程序,它们提供一些诸如函数之类的东西,使其他项目也能使用这些东西。大多数时间说的 crate 指的都是库,这与其他编程语言中 library 概念一致。
包(package)是提供一系列功能的一个或者多个 crate。一个包会包含一个 Cargo.toml 文件,阐述如何去构建这些 crate。Cargo 就是一个包含构建你代码的二进制项的包。Cargo 也包含这些二进制项所依赖的库。其他项目也能用 Cargo 库来实现与 Cargo 命令行程序一样的逻辑。
包中可以包含至多一个库 crate(library crate)。包中可以包含任意多个二进制 crate(binary crate),但是必须至少包含一个 crate(无论是库的还是二进制的)。
当我们创建一个项目的时候,看看发生了什么?
cargo new my-project
通过上图,我们可知Cargo 遵循的一个约定:src/main.rs 就是一个与包同名的二进制 crate 的 crate 根。同样的,Cargo 知道如果包目录中包含 src/lib.rs,则包带有与其同名的库 crate,且 src/lib.rs 是 crate 根。crate 根文件将由 Cargo 传递给 rustc
来实际构建库或者二进制项目。
在此,我们有了一个只包含 src/main.rs 的包,意味着它只含有一个名为 my-project
的二进制 crate。如果一个包同时含有 src/main.rs 和 src/lib.rs,则它有两个 crate:一个二进制的和一个库的,且名字都与包相同。通过将文件放在 src/bin 目录下,一个包可以拥有多个二进制 crate:每个 src/bin 下的文件都会被编译成一个独立的二进制 crate。
2、定义模块来控制作用域与私有性
在这里我们看下模块相关的一些知识点:
- 从 crate 根节点开始: 当编译一个 crate, 编译器首先在 crate 根文件(通常,对于一个库 crate 而言是src/lib.rs,对于一个二进制 crate 而言是src/main.rs)中寻找需要被编译的代码。
- 声明模块: 在 crate 根文件中,你可以声明一个新模块;比如,你用
mod garden
声明了一个叫做garden
的模块。编译器会在下列路径中寻找模块代码:- 内联,在大括号中,当
mod garden
后方不是一个分号而是一个大括号 - 在文件 src/garden.rs
- 在文件 src/garden/mod.rs
- 内联,在大括号中,当
- 声明子模块: 在除了 crate 根节点以外的其他文件中,你可以定义子模块。比如,你可能在src/garden.rs中定义了
mod vegetables;
。编译器会在以父模块命名的目录中寻找子模块代码:- 内联,在大括号中,当
mod vegetables
后方不是一个分号而是一个大括号 - 在文件 src/garden/vegetables.rs
- 在文件 src/garden/vegetables/mod.rs
- 内联,在大括号中,当
- 模块中的代码路径: 一旦一个模块是你 crate 的一部分,你可以在隐私规则允许的前提下,从同一个 crate 内的任意地方,通过代码路径引用该模块的代码。举例而言,一个 garden vegetables 模块下的
Asparagus
类型可以在crate::garden::vegetables::Asparagus
被找到。 - 私有 vs 公用: 一个模块里的代码默认对其父模块私有。为了使一个模块公用,应当在声明时使用
pub mod
替代mod
。为了使一个公用模块内部的成员公用,应当在声明前使用pub
。 use
关键字: 在一个作用域内,use
关键字创建了一个成员的快捷方式,用来减少长路径的重复。在任何可以引用crate::garden::vegetables::Asparagus
的作用域,你可以通过use crate::garden::vegetables::Asparagus;
创建一个快捷方式,然后你就可以在作用域中只写Asparagus
来使用该类型。
在前面的项目中,我们可以创建对应的模块和子模块,项目目录结构如下所示:
根据以上树型结构,我们先看一下src/main.rs 文件的内容。
use crate::garden::vegetables::Asparagus;pub mod garden;fn main() {let plant = Asparagus {color: String::from("green"),number: 10,};println!("{:?}!", plant);
}
pub mod garden,定义了一个菜园的公共模块,告诉rust编译器这个模块对应的文件是src/garden.rs,该文件对应的内容如下所示:
pub mod vegetables;
pub mod vegetables;声明了一个蔬菜的子模块,所以对应的模块文件时src/garden/vegetables.rs,在这个模块中,我们定义了一个结构体(芦笋,包含颜色,个数2个属性)如下所示:
#[derive(Debug)]
pub struct Asparagus {pub color: String,pub number: i32,
}
最后我们执行以下,main.rs中函数,结果如下所示:
2.1 在模块中对相关代码进行分组
模块 让我们可以将一个 crate 中的代码进行分组,以提高可读性与重用性。因为一个模块中的代码默认是私有的,所以还可以利用模块控制项的 私有性。私有项是不可为外部使用的内在详细实现。我们也可以将模块和它其中的项标记为公开的,这样,外部代码就可以使用并依赖与它们。
我们可以把前面的代码放入 src/lib.rs 中,来定义一些模块和函数。
pub mod garden {pub mod vegetables {#[derive(Debug)]pub struct Asparagus {pub color: String,pub number: i32,}}
}
在main.rs中,改写如下:
use my_project::garden::vegetables::Asparagus;
fn main() {let plant = Asparagus {color: String::from("green"),number: 10,};println!("res {:?}!", plant);
}
在src/lib.rs 中,我们可以把相关模块组合起来,菜园模块里包括蔬菜模块,蔬菜模块里包括芦笋的结构体,这样做我们可以把相关性功能进行聚合,也能便于以后项目的维护,同时也能使代码具有更好的可读性。
在前面我们提到了,src/main.rs
和 src/lib.rs
叫做 crate 根。之所以这样叫它们是因为这两个文件的内容都分别在 crate 模块结构的根组成了一个名为 crate
的模块,该结构被称为 模块树(module tree)。
crate
└─ garden└─ vegetables└─ Asparagus
3、引用模块项目的路径
ust 如何在模块树中找到一个项的位置,我们使用路径的方式,就像在文件系统使用路径一样。为了调用一个函数,我们需要知道它的路径。
路径有两种形式:
- 绝对路径(absolute path)是以 crate 根(root)开头的全路径;对于外部 crate 的代码,是以 crate 名开头的绝对路径,对于当前 crate 的代码,则以字面值
crate
开头。 - 相对路径(relative path)从当前模块开始,以
self
、super
或当前模块的标识符开头。
在文件名:src/lib.rs 中,看下示例绝对路径和相对路径的条用:
pub mod garden {pub mod vegetables {#[derive(Debug)]pub struct Asparagus {pub color: String,pub number: i32,}}
}pub fn eat_at_restaurant() {// 绝对路径crate::garden::vegetables::Asparagus {color: String::from("green"),number: 10,};// 相对路径garden::vegetables::Asparagus {color: String::from("green"),number: 10,};
}
在 Rust 中,默认所有项(函数、方法、结构体、枚举、模块和常量)对父模块都是私有的。如果希望创建一个私有函数或结构体,你可以将其放入一个模块。所以在以上代码中,我们增加pub关键字,如果不添加,运行会报以下错误:
3.1 使用 pub 关键字暴露路径
上图中,告诉我们 vegetables模块是私有的。如果我们在定义的时候,给对应的模块加上关键字pub 然后再看下结果:
pub mod garden {pub mod vegetables {#[derive(Debug)]struct Asparagus {color: String,number: i32,}}
}pub fn eat_at_restaurant() {// 绝对路径crate::garden::vegetables::Asparagus {color: String::from("green"),number: 10,};// 相对路径garden::vegetables::Asparagus {color: String::from("green"),number: 10,};
}
运行一下,结果如下所示:
根据上图中的结果,我们发现虽然模块添加了关键字pub,但是运行的时候还是报错,错误提示说Asparagus 是私有的结构体。如果这样那么我们需要在定义的结构体前面也需要添加pub关键字,如下所示:
pub mod garden {pub mod vegetables {#[derive(Debug)]pub struct Asparagus {color: String,number: i32,}}
}pub fn eat_at_restaurant() {// 绝对路径crate::garden::vegetables::Asparagus {color: String::from("green"),number: 10,};// 相对路径garden::vegetables::Asparagus {color: String::from("green"),number: 10,};
}
运行一下,结果如下所示:
根据上图结果所示,提示结构体中定义的字段也是私有,那么我们可以把对应的字段也添加上关键字pub。
二进制和库 crate 包的最佳实践
我们提到过包可以同时包含一个 src/main.rs 二进制 crate 根和一个 src/lib.rs 库 crate 根,并且这两个 crate 默认以包名来命名。通常,这种包含二进制 crate 和库 crate 的模式的包,在二进制 crate 中只有足够的代码来启动一个可执行文件,可执行文件调用库 crate 的代码。又因为库 crate 可以共享,这使得其它项目从包提供的大部分功能中受益。
模块树应该定义在 src/lib.rs 中。这样通过以包名开头的路径,公有项就可以在二进制 crate 中使用。二进制 crate 就完全变成了同其它 外部 crate 一样的库 crate 的用户:它只能使用公有 API。这有助于你设计一个好的 API;
3.2 super 开始的相对路径
我们可以通过在路径的开头使用 super
,从父模块开始构建相对路径,而不是从当前模块或者 crate 根开始。这类似以 ..
语法开始一个文件系统路径。使用 super
允许我们引用父模块中的已知项,这使得重新组织模块树变得更容易 —— 当模块与父模块关联的很紧密,但某天父模块可能要移动到模块树的其它位置。
我们看一下如下示例,就很容易理解了。
pub mod garden {pub mod vegetables {fn get_asparagus() {super::get_garden()}}fn get_garden() {println!("getGarden ...")}
}
所以我们可以使用 super
进入父级模块,就可以引用父级模块中的一些函数、结构体等。
3.3 创建公有的结构体和枚举
我们还可以使用 pub
来设计公有的结构体和枚举,不过关于在结构体和枚举上使用 pub
还有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 pub
,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有。所以在之前的示例中如果没有把结构体中的变量声明为公有,所以后面就报错了,为了程序运行对引用结构体的变量也要声明为公有的,如下所示:
pub mod garden {pub mod vegetables {#[derive(Debug)]pub struct Asparagus {pub color: String,pub number: i32,}}
}pub fn eat_at_restaurant() {// 绝对路径crate::garden::vegetables::Asparagus {color: String::from("green"),number: 10,};// 相对路径garden::vegetables::Asparagus {color: String::from("green"),number: 10,};
}
如果我们将枚举设为公有,则它的所有成员都将变为公有。我们只需要在 enum
关键字前面加上 pub
,如下所示:
pub mod people {pub enum Sex {Man,Woman,}
}
fn get_sex() {let man = people::Sex::Man;
}
总结:
因此枚举成员默认就是公有的。结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用
pub
关键字。
相关文章:

使用包、Crate 和模块管理项目(上)
目录 1、包和Crate 2、定义模块来控制作用域与私有性 2.1 在模块中对相关代码进行分组 3、引用模块项目的路径 3.1 使用 pub 关键字暴露路径 二进制和库 crate 包的最佳实践 3.2 super 开始的相对路径 3.3 创建公有的结构体和枚举 Rust 有许多功能可以让你管理代码的组…...

【Kotlin】
Lambda 就是一小段可以作为参数传递的代码。 因为正常情况下,我们向某个函数传参时只能传入变量,而借助Lambda 却允许传入一小段代码。 Lambda 表达式的语法结构: {参数名1: 参数类型, 参数名2: 参数类型 -> 函数体}首先,最外…...

JavaDay17
创建不可变集合 import java.util.Iterator; import java.util.List;public class Test {public static void main(String[] args) {/*创建不可变的List集合* "张三" "李四" "王五" "赵六*///一旦创建之后 是无法进行修改的 在下面的代码…...

Python爬取酷我音乐
🎈 博主:一只程序猿子 🎈 博客主页:一只程序猿子 博客主页 🎈 个人介绍:爱好(bushi)编程! 🎈 创作不易:喜欢的话麻烦您点个👍和⭐! 🎈…...

项目实战第四十七讲:易宝支付对接详解(保姆级教程)
易宝支付对接(保姆级教程) 为了实现项目的支付需求,公司选择了易宝支付进行对接,本文是项目实战第四十七讲,详解易宝支付对接。 文章目录 易宝支付对接(保姆级教程)1、需求背景2、流程图3、技术方案4、相关接口4.1、入驻相关(商户入网)4.2、账户相关接口(充值、提现、…...

python的websocket方法教程
WebSocket是一种网络通信协议,它在单个TCP连接上提供全双工的通信信道。在本篇文章中,我们将探讨如何在Python中使用WebSocket实现实时通信。 websockets是Python中最常用的网络库之一,也是websocket协议的Python实现。它不仅作为基础组件在…...

Qt处理焦点事件(获得焦点,失去焦点)
背景: 我只是想处理焦点动作,由于懒,上网一搜,排名靠前的一位朋友,使用重写部件的方式实现。还是因为懒,所以感觉复杂了。于是又花了一分钟解决了一下。 所以记录下来,以免以后忘了。 思路&a…...

SiteGround如何设置WordPress网站自动更新
SiteGround Autoupdate功能会自动帮我们更新在他们这里托管的所有WordPress网站,这样做是为了保证网站安全,并且让它们一直保持最新状态。他们会根据我们选择的设置自动更新不同版本的WordPress,包括主要版本和次要版本。在每次自动更新之前&…...

http代理和SOCK5代理谁更安全?
在这个网络化的时代,我们常常听到HTTP代理和SOCKS5代理这两个名词,不过很多人并不了解是什么意思。今天,我们将揭开这两种代理的神秘面纱,看看到底HTTP代理和SOCKS5代理哪个更安全? HTTP代理:高效通信的“枢…...

Kotlin关键字二——constructor和init
在关键字一——var和val中最后提到了构造函数,这里就学习下构造函数相关的关键字: constructor和init。 主要构造(primary constructor) kotlin和java一样,在定义类时就自动生成了无参构造 // 会生成默认的无参构造函数 class Person{ }与java不同的是…...

java的long类型超过9位报错:the literal 987654321000 of type int is out of range
java的long类型超过9位报错 1、报错提示2、报错截图3、解决办法4、参考文章 1、报错提示 the literal 987654321000 of type int is out of range 2、报错截图 3、解决办法 long类型是一种用于表示较大整数的数据类型,范围比int类型更广泛。然而,即使…...

【Java期末复习资料】(4)模拟卷
有不会的题可以后台问我的哦,看见了就会回。 本文章主要是模拟卷,知识点例题简答题已经发过了,可以在主页专栏Java中找一下 一、单项选择题 1. 编译 Java Application 源程序文件将产生相应的字节码文件,这些字节码文件的扩展名为…...

【计算机网络】UDP报文详解
目录 一. UDP协议概述 二. UDP报文格式 首部 三. UDP的缓冲区 一. UDP协议概述 UDP——用户数据报协议,是传输层的一个重要协议 基于UDP的应用层协议有:DNS,TFTP,SNMP,NTP 协议全称默认端口号DNSDomain Name Se…...

排序算法——归并排序
归并排序(Merge Sort)是计算机科学中非常重要的排序算法之一。它不仅高效、稳定,而且是许多高级排序技术和算法思想的基础。在本文中,我们将深入探讨归并排序的原理、实现方法,以及它的优缺点。 1. 归并排序的原理 归…...

2023 年安徽省职业院校技能大赛高职组“软件测试”赛项样题
2023 年安徽省职业院校技能大赛 高职组“软件测试”赛项样题 目录 任务一:功能测试(45 分) 1、测试计划(5 分) 2、测试用例(15 分) 3、Bug 清单(20 分) 4、测试报告&…...

Mysql8和Oracle实际项目中递归查询树形结构
背景: 项目升级,引入MySQL数据库,之前一直用的是Oracle数据,在做用户登录单位维护的时候,需要返回该用户所属单位下的所有子单位。下边是模拟项目数据实践的过程。 数据准备: 准备一张单位表,…...

docker mysql8 设置不区分大小写
docker安装Mysql8.0的坑之lower_case_table_names_docker mysql lower_case_table_names-CSDN博客https://blog.csdn.net/p793049488/article/details/108365929 docker run ‐di ‐‐nametensquare_mysql ‐p 33306:3306 ‐e MYSQL_ROOT_PASSWORD123456 mysql...

Audio Siganl (MATLAB) 代码学习—常见问题3
问题描述 生成信号y1: 8000个样本,1000个周期,幅度为0.85的余弦信号。若信号的持续时间为1s,则采样频率和信号频率为多少。生成信号y2: 持续时间为1s,幅度为0.7,频率为500Hz,相位为 π / 4 \pi/4 π/4生成信号y:y_1+y_2绘制前200ms的y信号示意图计算y的DFT绘制频域示意图…...

【PTA题目】7-8 矩阵运算 分数 10
7-8 矩阵运算 分数 10 全屏浏览题目 切换布局 作者 C课程组 单位 浙江大学 给定一个nn的方阵,本题要求计算该矩阵除副对角线、最后一列和最后一行以外的所有元素之和。副对角线为从矩阵的右上角至左下角的连线。 输入格式: 输入第一行给出正整数n(…...

Ubuntu20.04创建并挂在zfs池
Ubuntu 下使用 ZFS [适用于中高级用户] 主磁盘上清洁安装带有ZFS的Ubuntu后,可以开始体验其特性。 所有ZFS配置过程都需要命令行。 我不知道有GUI工具。 创建一个 ZFS 池 本节仅适用于具有多个磁盘的系统。 如果只有一个磁盘,Ubuntu会在安装时自动创建…...

x的平方根算法(leetcode第69题)
题目描述: 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。…...

打破空间限制,畅享真实生活
直播已经成为了当今社会中非常流行的一种娱乐方式,也是人们获取信息和互动的重要渠道之一。而无绿幕直播,则是近年来兴起的一种特殊形式,它打破了以往直播的空间限制,让观众们能够更贴近主播,更真实地感受到直播背后的…...

Python基础期末复习 新手 2
虽然age 10在__init__方法中定义了一个局部变量age,但这个局部变量并不会影响类属性age的值。类属性是在类级别上定义的,不属于任何一个实例。因此,在创建实例s1和s2时,它们的age属性值都为类属性的初始值0。 尽管对类的属性值进…...

Java接入ChatGPT接口简单示例
我们定义了一个名为ChartGPTConfig的类,它有两个私有成员变量apiKey和apiUrl,分别表示ChartGPT的API密钥和API URL。 public class ChartGPTConfig {private final String apiKey;private final String apiUrl;public ChartGPTConfig(String apiKey, St…...

解决夜神模拟器与Android studio自动断开的问题
原因:夜神模拟器的adb版本和Android sdk的adb版本不一致 解决办法: 1.找到android的sdk (1)File--->Project Structure (2)SDK Location:记下sdk的位置 2.找到sdk中的adb文件 SDK-->platform-tools-->adb.exe 3.复制…...

利用C语言模拟实现堆的基本操作和调堆算法
利用C语言模拟实现堆的基本操作和调堆算法 文章目录 利用C语言模拟实现堆的基本操作和调堆算法前言一、堆的基本原理大根堆和小根堆的比较 二、实现堆的基本操作1)结构定义2)初始化堆(HeapInit)3)销毁堆(He…...

react hooks之useRef和useImperativeHandle
为什么这两个一起写,是因为这两个关联性很大,逐一介绍。 一:useRef 1、作用:用于在函数组件中创建一个持久化的引用变量。这个引用变量可以在组件的多次渲染之间保持不变,并且可以访问和修改 DOM 元素或其他组件实例…...

scala方法与函数
定义方法定义函数方法和函数的区别scala的方法函数操作 1.9 方法与函数 1.9.1 定义方法 定义方法的基本格式是: def 方法名称(参数列表):返回值类型 方法体 def add(x: Int, y: Int): Int x y println(add(1, 2)) // 3 //也…...

前端框架(Front-end Framework)和库(Library)的区别
聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 欢迎来到前端入门之旅!感兴趣的可以订阅本专栏哦!这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…...

mysql原理--B+树索引的使用
1.索引的代价 在介绍如何更好的使用索引之前先要了解一下使用这玩意儿的代价,它在空间和时间上都会拖后腿: (1). 空间上的代价 这个是显而易见的,每建立一个索引都要为它建立一棵 B 树,每一棵 B 树的每一个节点都是一个数据页&…...