路径用于引用模块树中的项
来看一下 Rust 如何在模块树中找到一个项的位置,我们使用路径的方式,就像在文件系统使用路径一样。如果我们想要调用一个函数,我们需要知道它的路径。
路径有两种形式:
- 绝对路径(absolute path)从 crate 根部开始,以 crate 名或者字面量
crate
开头。 - 相对路径(relative path)从当前模块开始,以
self
、super
或当前模块的标识符开头。
绝对路径和相对路径都后跟一个或多个由双冒号(::
)分割的标识符。
让我们回到示例 7-1。我们如何调用 add_to_waitlist
函数?还是同样的问题,add_to_waitlist
函数的路径是什么?在示例 7-3 中,我们通过删除一些模块和函数,稍微简化了一下我们的代码。我们在 crate 根部定义了一个新函数 eat_at_restaurant
,并在其中展示调用 add_to_waitlist
函数的两种方法。eat_at_restaurant
函数是我们 crate 库的一个公共 API,所以我们使用 pub
关键字来标记它。在“使用 pub
关键字暴露路径”
一节,我们将详细介绍 pub
。注意,这个例子无法编译通过,我们稍后会解释原因。
文件名: src/lib.rs
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-03/src/lib.rs}}
示例 7-3: 使用绝对路径和相对路径来调用 add_to_waitlist
函数
第一种方式,我们在 eat_at_restaurant
中调用 add_to_waitlist
函数,使用的是绝对路径。add_to_waitlist
函数与 eat_at_restaurant
被定义在同一 crate 中,这意味着我们可以使用 crate
关键字为起始的绝对路径。
在 crate
后面,我们持续地嵌入模块,直到我们找到 add_to_waitlist
。你可以想象出一个相同结构的文件系统,我们通过指定路径 /front_of_house/hosting/add_to_waitlist
来执行 add_to_waitlist
程序。我们使用 crate
从 crate 根部开始就类似于在 shell 中使用 /
从文件系统根开始。
第二种方式,我们在 eat_at_restaurant
中调用 add_to_waitlist
,使用的是相对路径。这个路径以 front_of_house
为起始,这个模块在模块树中,与 eat_at_restaurant
定义在同一层级。与之等价的文件系统路径就是 front_of_house/hosting/add_to_waitlist
。以名称为起始,意味着该路径是相对路径。
选择使用相对路径还是绝对路径,还是要取决于你的项目。取决于你是更倾向于将项的定义代码与使用该项的代码分开来移动,还是一起移动。举一个例子,如果我们要将 front_of_house
模块和 eat_at_restaurant
函数一起移动到一个名为 customer_experience
的模块中,我们需要更新 add_to_waitlist
的绝对路径,但是相对路径还是可用的。然而,如果我们要将 eat_at_restaurant
函数单独移到一个名为 dining
的模块中,还是可以使用原本的绝对路径来调用 add_to_waitlist
,但是相对路径必须要更新。我们更倾向于使用绝对路径,因为把代码定义和项调用各自独立地移动是更常见的。
让我们试着编译一下示例 7-3,并查明为何不能编译!示例 7-4 展示了这个错误。
{{#include ../listings/ch07-managing-growing-projects/listing-07-03/output.txt}}
示例 7-4: 构建示例 7-3 出现的编译器错误
错误信息说 hosting
模块是私有的。换句话说,我们拥有 hosting
模块和 add_to_waitlist
函数的的正确路径,但是 Rust 不让我们使用,因为它不能访问私有片段。
模块不仅对于你组织代码很有用。他们还定义了 Rust 的 私有性边界(privacy boundary):这条界线不允许外部代码了解、调用和依赖被封装的实现细节。所以,如果你希望创建一个私有函数或结构体,你可以将其放入模块。
Rust 中默认所有项(函数、方法、结构体、枚举、模块和常量)都是私有的。父模块中的项不能使用子模块中的私有项,但是子模块中的项可以使用他们父模块中的项。这是因为子模块封装并隐藏了他们的实现详情,但是子模块可以看到他们定义的上下文。继续拿餐馆作比喻,把私有性规则想象成餐馆的后台办公室:餐馆内的事务对餐厅顾客来说是不可知的,但办公室经理可以洞悉其经营的餐厅并在其中做任何事情。
Rust 选择以这种方式来实现模块系统功能,因此默认隐藏内部实现细节。这样一来,你就知道可以更改内部代码的哪些部分而不会破坏外部代码。你还可以通过使用 pub
关键字来创建公共项,使子模块的内部部分暴露给上级模块。
使用 pub
关键字暴露路径
让我们回头看一下示例 7-4 的错误,它告诉我们 hosting
模块是私有的。我们想让父模块中的 eat_at_restaurant
函数可以访问子模块中的 add_to_waitlist
函数,因此我们使用 pub
关键字来标记 hosting
模块,如示例 7-5 所示。
文件名: src/lib.rs
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-05/src/lib.rs}}
示例 7-5: 使用 pub
关键字声明 hosting
模块使其可在 eat_at_restaurant
使用
不幸的是,示例 7-5 的代码编译仍然有错误,如示例 7-6 所示。
{{#include ../listings/ch07-managing-growing-projects/listing-07-05/output.txt}}
示例 7-6: 构建示例 7-5 出现的编译器错误
发生了什么?在 mod hosting
前添加了 pub
关键字,使其变成公有的。伴随着这种变化,如果我们可以访问 front_of_house
,那我们也可以访问 hosting
。但是 hosting
的 内容(contents) 仍然是私有的;这表明使模块公有并不使其内容也是公有的。模块上的 pub
关键字只允许其父模块引用它。
示例 7-6 中的错误说,add_to_waitlist
函数是私有的。私有性规则不但应用于模块,还应用于结构体、枚举、函数和方法。
让我们继续将 pub
关键字放置在 add_to_waitlist
函数的定义之前,使其变成公有。如示例 7-7 所示。
文件名: src/lib.rs
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-07/src/lib.rs}}
示例 7-7: 为 mod hosting
和 fn add_to_waitlist
添加 pub
关键字使他们可以在
eat_at_restaurant
函数中被调用
现在代码可以编译通过了!让我们看看绝对路径和相对路径,并根据私有性规则,再检查一下为什么增加 pub
关键字使得我们可以在 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
开头来构建从父模块开始的相对路径。这么做类似于文件系统中以 ..
开头的语法。我们为什么要这样做呢?
考虑一下示例 7-8 中的代码,它模拟了厨师更正了一个错误订单,并亲自将其提供给客户的情况。fix_incorrect_order
函数通过指定的 super
起始的 serve_order
路径,来调用 serve_order
函数:
文件名: src/lib.rs
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-08/src/lib.rs}}
示例 7-8: 使用以 super
开头的相对路径从父目录开始调用函数
fix_incorrect_order
函数在 back_of_house
模块中,所以我们可以使用 super
进入 back_of_house
父模块,也就是本例中的 crate
根。在这里,我们可以找到 serve_order
。成功!我们认为 back_of_house
模块和 serve_order
函数之间可能具有某种关联关系,并且,如果我们要重新组织这个 crate 的模块树,需要一起移动它们。因此,我们使用 super
,这样一来,如果这些代码被移动到了其他模块,我们只需要更新很少的代码。
创建公有的结构体和枚举
我们还可以使用 pub
来设计公有的结构体和枚举,不过有一些额外的细节需要注意。如果我们在一个结构体定义的前面使用了 pub
,这个结构体会变成公有的,但是这个结构体的字段仍然是私有的。我们可以根据情况决定每个字段是否公有。在示例 7-9 中,我们定义了一个公有结构体 back_of_house::Breakfast
,其中有一个公有字段 toast
和私有字段 seasonal_fruit
。这个例子模拟的情况是,在一家餐馆中,顾客可以选择随餐附赠的面包类型,但是厨师会根据季节和库存情况来决定随餐搭配的水果。餐馆可用的水果变化是很快的,所以顾客不能选择水果,甚至无法看到他们将会得到什么水果。
文件名: src/lib.rs
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-09/src/lib.rs}}
示例 7-9: 带有公有和私有字段的结构体
因为 back_of_house::Breakfast
结构体的 toast
字段是公有的,所以我们可以在 eat_at_restaurant
中使用点号来随意的读写 toast
字段。注意,我们不能在 eat_at_restaurant
中使用 seasonal_fruit
字段,因为 seasonal_fruit
是私有的。尝试去除那一行修改 seasonal_fruit
字段值的代码的注释,看看你会得到什么错误!
还请注意一点,因为 back_of_house::Breakfast
具有私有字段,所以这个结构体需要提供一个公共的关联函数来构造 Breakfast
的实例(这里我们命名为 summer
)。如果 Breakfast
没有这样的函数,我们将无法在 eat_at_restaurant
中创建 Breakfast
实例,因为我们不能在 eat_at_restaurant
中设置私有字段 seasonal_fruit
的值。
与之相反,如果我们将枚举设为公有,则它的所有成员都将变为公有。我们只需要在 enum
关键字前面加上 pub
,就像示例 7-10 展示的那样。
文件名: src/lib.rs
{{#rustdoc_include ../listings/ch07-managing-growing-projects/listing-07-10/src/lib.rs}}
示例 7-10: 设计公有枚举,使其所有成员公有
因为我们创建了名为 Appetizer
的公有枚举,所以我们可以在 eat_at_restaurant
中使用 Soup
和 Salad
成员。如果枚举成员不是公有的,那么枚举会显得用处不大;给枚举的所有成员挨个添加 pub
是很令人恼火的,因此枚举成员默认就是公有的。结构体通常使用时,不必将它们的字段公有化,因此结构体遵循常规,内容全部是私有的,除非使用 pub
关键字。
还有一种使用 pub
的场景我们还没有涉及到,那就是我们最后要讲的模块功能:use
关键字。我们将先单独介绍 use
,然后展示如何结合使用 pub
和 use
。
推荐几款学习编程的免费平台
免费在线开发平台(https://docs.ltpp.vip/LTPP/)
探索编程世界的新天地,为学生和开发者精心打造的编程平台,现已盛大开启!这个平台汇集了近4000道精心设计的编程题目,覆盖了C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等众多编程语言,为您的编程学习之旅提供了一个全面而丰富的实践环境。
在这里,您不仅可以查看自己的代码记录,还能轻松地在云端保存和运行代码,让编程变得更加便捷。平台还提供了私聊和群聊功能,让您可以与同行们无障碍交流,分享文件,共同进步。不仅如此,您还可以通过阅读文章、参与问答板块和在线商店,进一步拓展您的知识边界。
为了提升您的编程技能,平台还设有每日一题、精选题单以及激动人心的编程竞赛,这些都是备考编程考试的绝佳资源。更令人兴奋的是,您还可以自定义系统UI,选择视频或图片作为背景,打造一个完全个性化的编码环境,让您的编程之旅既有趣又充满挑战。
免费公益服务器(https://docs.ltpp.vip/LTPP-SHARE/linux.html)
作为开发者或学生,您是否经常因为搭建和维护编程环境而感到头疼?现在,您不必再为此烦恼,因为一款全新的免费公共服务器已经为您解决了所有问题。这款服务器内置了多种编程语言的编程环境,并且配备了功能强大的在线版VS Code,让您可以随时随地在线编写代码,无需进行任何复杂的配置。
随时随地,云端编码
无论您身在何处,只要有网络连接,就可以通过浏览器访问这款公共服务器,开始您的编程之旅。这种云端编码的便利性,让您的学习或开发工作不再受限于特定的设备或环境。
丰富的编程语言支持
服务器支持包括C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#等在内的多种主流编程语言,满足不同开发者和学生的需求。无论您是初学者还是资深开发者,都能找到适合自己的编程环境。
在线版VS Code,高效开发
内置的在线版VS Code提供了与本地VS Code相似的编辑体验,包括代码高亮、智能提示、代码调试等功能,让您即使在云端也能享受到高效的开发体验。
数据隐私和安全提醒
虽然服务器是免费的,但为了保护您的数据隐私和安全,我们建议您不要上传任何敏感或重要的数据。这款服务器更适合用于学习和实验,而非存储重要信息。
免费公益MYSQL(https://docs.ltpp.vip/LTPP-SHARE/mysql.html)
作为一名开发者或学生,数据库环境的搭建和维护往往是一个复杂且耗时的过程。但不用担心,现在有一款免费的MySQL服务器,专为解决您的烦恼而设计,让数据库的使用变得简单而高效。
性能卓越,满足需求
虽然它是免费的,但性能绝不打折。服务器提供了稳定且高效的数据库服务,能够满足大多数开发和学习场景的需求。
在线phpMyAdmin,管理更便捷
内置的在线phpMyAdmin管理面板,提供了一个直观且功能强大的用户界面,让您可以轻松地查看、编辑和管理数据库。
数据隐私提醒,安全第一
正如您所知,这是一项公共资源,因此我们强烈建议不要上传任何敏感或重要的数据。请将此服务器仅用于学习和实验目的,以确保您的数据安全。
免费在线WEB代码编辑器(https://docs.ltpp.vip/LTPP-WEB-IDE/)
无论你是开发者还是学生,编程环境的搭建和管理可能会占用你宝贵的时间和精力。现在,有一款强大的免费在线代码编辑器,支持多种编程语言,让您可以随时随地编写和运行代码,提升编程效率,专注于创意和开发。
多语言支持,无缝切换
这款在线代码编辑器支持包括C、C++、JavaScript、TypeScript、Go、Rust、PHP、Java、Ruby、Python3以及C#在内的多种编程语言,无论您的项目需要哪种语言,都能在这里找到支持。
在线运行,快速定位问题
您可以在编写代码的同时,即时运行并查看结果,快速定位并解决问题,提高开发效率。
代码高亮与智能提示
编辑器提供代码高亮和智能提示功能,帮助您更快地编写代码,减少错误,提升编码质量。
免费二维码生成器(https://docs.ltpp.vip/LTPP-QRCODE/)
二维码(QR Code)是一种二维条码,能够存储更多信息,并且可以通过智能手机等设备快速扫描识别。它广泛应用于各种场景,如:
企业宣传
企业可以通过二维码分享公司网站、产品信息、服务介绍等。
活动推广
活动组织者可以创建二维码,参与者扫描后可以直接访问活动详情、报名链接或获取电子门票。
个人信息分享
个人可以生成包含联系方式、社交媒体链接、个人简历等信息的二维码。
电子商务
商家使用二维码进行商品追踪、促销活动、在线支付等。
教育
教师可以创建二维码,学生扫描后可以直接访问学习资料或在线课程。
交通出行
二维码用于公共交通的票务系统,乘客扫描二维码即可进出站或支付车费。 功能强大的二维码生成器通常具备用户界面友好,操作简单,即使是初学者也能快速上手和生成的二维码可以在各种设备和操作系统上扫描识别的特点。