rust-模块树中引用项的路径

模块树中引用项的路径

为了告诉 Rust 在模块树中如何找到某个项,我们使用路径,就像在文件系统中导航时使用路径一样。要调用一个函数,我们需要知道它的路径。

路径有两种形式:

  • 绝对路径是从 crate 根开始的完整路径;对于外部 crate 的代码,绝对路径以 crate 名称开头,对于当前 crate 的代码,则以字面量 crate 开头。
  • 相对路径从当前模块开始,使用 self、super 或当前模块中的标识符。

无论是绝对还是相对路径,都由一个或多个用双冒号 (::) 分隔的标识符组成。

回到清单 7-1,假设我们想调用 add_to_waitlist 函数。这等同于问:add_to_waitlist 函数的路径是什么?清单 7-3 包含了删除了一些模块和函数后的清单 7-1。

我们将展示两种方法,从定义在 crate 根的新函数 eat_at_restaurant 中调用 add_to_waitlist 函数。这些路径都是正确的,但还有另一个问题会导致此示例无法编译。稍后我们会解释原因。

eat_at_restaurant 函数是我们的库 crate 公共 API 的一部分,因此我们用 pub 关键字标记它。在“使用 pub 关键字暴露路径”一节中,我们将详细介绍 pub。

文件名:src/lib.rs

mod front_of_house {mod hosting {fn add_to_waitlist() {}}
}pub fn eat_at_restaurant() {// Absolute pathcrate::front_of_house::hosting::add_to_waitlist();// Relative pathfront_of_house::hosting::add_to_waitlist();
}

清单7-3:使用绝对路径和相对路径调用add_to_waitlist函数
第一次在eat_at_restaurant中调用add_to_waitlist函数时,我们使用了绝对路径。add_to_waitlist函数定义在与eat_at_restaurant相同的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所示。

$ cargo buildCompiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private--> src/lib.rs:9:28|
9 |     crate::front_of_house::hosting::add_to_waitlist();|                            ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported|                            ||                            private module|
note: the module `hosting` is defined here--> src/lib.rs:2:5|
2 |     mod hosting {|     ^^^^^^^^^^^error[E0603]: module `hosting` is private--> src/lib.rs:12:21|
12 |     front_of_house::hosting::add_to_waitlist();|                     ^^^^^^^  --------------- function `add_to_waitlist` is not publicly re-exported|                     ||                     private module|
note: the module `hosting` is defined here--> src/lib.rs:2:5|
2  |     mod hosting {|     ^^^^^^^^^^^For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors

列表 7-4:构建列表 7-3 中代码时的编译器错误
错误信息显示模块 hosting 是私有的。换句话说,我们为 hosting 模块和 add_to_waitlist 函数指定了正确的路径,但 Rust 不允许我们使用它们,因为无法访问私有部分。在 Rust 中,所有项(函数、方法、结构体、枚举、模块和常量)默认对父模块是私有的。如果你想让某个项如函数或结构体变成私有,可以将其放入一个模块中。

父模块中的项不能使用子模块内的私有项,但子模块中的项可以使用其祖先模块中的项。这是因为子模块封装并隐藏了它们的实现细节,但子模块能看到定义它们的上下文。继续用我们的比喻,隐私规则就像餐厅后台:那里发生的一切对顾客来说是保密的,但办公室经理可以看到并操作他们管理的整个餐厅。

Rust 选择让模块系统这样运作,是为了默认隐藏内部实现细节。这样,你就知道哪些内部代码部分可以更改而不会破坏外部代码。然而,Rust 确实提供了通过 pub 关键字将子模块代码中的内部部分公开给外层祖先模块的方法。

用 pub 关键字暴露路径

回到列表 7-4 中提示 hosting 模块是私有的问题。我们希望父模块中的 eat_at_restaurant 函数能够访问子模块中的 add_to_waitlist 函数,因此我们在 hosting 模块前加上 pub 关键字,如列表 7-5 所示。

文件名:src/lib.rs

mod front_of_house {pub mod hosting {fn add_to_waitlist() {}}
}// -- snip --

清单7-5:将宿主模块声明为pub以便从eat_at_restaurant中使用
不幸的是,清单7-5中的代码仍然会导致编译错误,如清单7-6所示。

$ cargo buildCompiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private--> src/lib.rs:10:37|
10 |     crate::front_of_house::hosting::add_to_waitlist();|                                     ^^^^^^^^^^^^^^^ private function|
note: the function `add_to_waitlist` is defined here--> src/lib.rs:3:9|
3  |         fn add_to_waitlist() {}|         ^^^^^^^^^^^^^^^^^^^^error[E0603]: function `add_to_waitlist` is private--> src/lib.rs:13:30|
13 |     front_of_house::hosting::add_to_waitlist();|                              ^^^^^^^^^^^^^^^ private function|
note: the function `add_to_waitlist` is defined here--> src/lib.rs:3:9|
3  |         fn add_to_waitlist() {}|         ^^^^^^^^^^^^^^^^^^^^For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors

清单 7-6:构建清单 7-5 中代码时的编译器错误
发生了什么?在 mod hosting 前添加 pub 关键字使模块变为公共。通过此更改,如果我们可以访问 front_of_house,就能访问 hosting。但 hosting 的内容仍然是私有的;将模块设为公共并不会使其内容公开。模块上的 pub 关键字只允许其祖先模块中的代码引用它,而不能访问其内部代码。因为模块是容器,仅仅将模块设为公共作用不大;我们需要进一步选择将模块内的一个或多个项也设为公共。

清单 7-6 中的错误提示 add_to_waitlist 函数是私有的。隐私规则适用于结构体、枚举、函数和方法以及模块。

让我们通过在定义前添加 pub 关键字,使 add_to_waitlist 函数也变成公有,如清单 7-7 所示。

文件名:src/lib.rs

mod front_of_house {  pub mod hosting {  pub fn add_to_waitlist() {}  }  
}  // -- 略 --

清单 7-7:给 mod hosting 和 fn add_to_waitlist 添加 pub 关键字后,我们可以从 eat_at_restaurant 调用该函数
现在代码可以编译了!为了理解为什么添加 pub 可以让我们根据隐私规则在 eat_at_restaurant 中使用这些路径,让我们看看绝对路径和相对路径。

在绝对路径中,我们以 crate(crate 模块树根)开始。front_of_house 模块定义于 crate 根目录下。虽然 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,其余部分也有效,这个函数调用合法!

如果你计划共享你的库 crate,以便其他项目使用你的代码,那么你的公共 API 就是你与用户之间确定如何交互代码的契约。在管理公共 API 更改方面,有许多考虑因素,以便他人更容易依赖你的 crate。这些内容超出本书范围;如果感兴趣,请参阅《Rust API 指南》。

同时包含二进制和库包的最佳实践

之前提到,一个包既可以包含 src/main.rs(二进制 crate 根),又可以包含 src/lib.rs(库 crate 根),默认两者都使用包名作为名称。这种同时含有库和二进制 crate 的包通常会在二进制 crate 中只写足够启动可执行程序并调用库中的代码,从而让其他项目能够利用该包提供的大部分功能,因为库中的代码可复用。

应当把模块树定义在 src/lib.rs 中,然后任何公有项都能通过以包名开头的路径,在二进制 crate 中使用。这样,二进制 crate 成为了库 crate 的用户,就像完全外部的另一个crate一样,只能使用公有 API。这帮助你设计良好的 API——不仅你是作者,同时也是客户!

第12章中,我们将演示这种组织方式,通过一个命令行程序,该程序既包含二进制crate,也包含库crate。
以 super 开头的相对路径
我们可以通过在路径开头使用 super 来构造从父模块开始的相对路径,而不是当前模块或 crate 根。这样类似于文件系统中以 … 语法开头的路径。使用 super 可以引用我们知道位于父模块中的项,这使得当模块与父模块关系密切但父模块将来可能会被移动到其他位置时,重新组织模块树更加方便。

考虑清单 7-8 中的代码,它模拟了厨师修正错误订单并亲自送给顾客的情景。在 back_of_house 模块中定义的 fix_incorrect_order 函数通过指定以 super 开头的 deliver_order 路径调用了定义在父模块中的 deliver_order 函数。

文件名:src/lib.rs

fn deliver_order() {}mod back_of_house {fn fix_incorrect_order() {cook_order();super::deliver_order();}fn cook_order() {}
}

清单 7-8:使用以 super 开头的相对路径调用函数
fix_incorrect_order 函数位于 back_of_house 模块,因此我们可以用 super 返回到 back_of_house 的父模块,在这里是 crate 根。从那里查找 deliver_order 并找到它。成功!我们认为 back_of_house 模块和 deliver_order 函数很可能保持这种关系,并且如果决定重组 crate 的模块树,它们会一起被移动。因此,我们用了 super,这样如果这段代码以后移到别的模块,只需更新更少的位置。

让结构体和枚举公开

我们也可以用 pub 将结构体和枚举设为公开,但对于结构体和枚举来说,pub 的用法有一些额外细节。如果在结构体定义前加上 pub,则该结构体是公开的,但其字段仍然是私有的。字段是否公开,可以逐个设置。在清单 7-9 中,我们定义了一个公共的 back_of_house::Breakfast 结构体,其中 toast 字段是公有,而 seasonal_fruit 字段是私有。这模拟餐厅里顾客能选择配餐面包种类,但厨师根据季节库存决定搭配水果这一情况。可选水果变化快,所以顾客既不能选择,也看不到具体是什么水果。

文件名:src/lib.rs

mod back_of_house {pub struct Breakfast {pub toast: String,seasonal_fruit: String,}impl Breakfast {pub fn summer(toast: &str) -> Breakfast {Breakfast {toast: String::from(toast),seasonal_fruit: String::from("peaches"),}}}
}pub fn eat_at_restaurant() {// 夏天点一份带黑麦吐司(Rye)的早餐。let mut meal = back_of_house::Breakfast::summer("Rye");// 改变想要面包种类。meal.toast = String::from("Wheat");println!("我想要 {} 吐司,谢谢", meal.toast);// 如果取消注释下一行,将无法编译;因为不允许访问或修改随餐附带季节性水果。// meal.seasonal_fruit = String::from("blueberries");
}

清单 7-9:部分字段公有、部分字段私有的结构体
由于 back_of_house::Breakfast 的 toast 字段是公有,在 eat_at_restaurant 中可以通过点号访问读写该字段。但不能访问 seasonal_fruit,因为它是私有字段。尝试取消注释修改 seasonal_fruit 那行代码,会看到编译错误!

另外,由于 BackOfHouse 有私有字段,该结构必须提供一个公共关联函数用于创建实例(此处命名为 summer)。否则,在 eat_at_restaurant 无法创建 Breakfast 实例,因为无法设置 private 字段值。

相比之下,如果将枚举设为 public,那么所有变体都是 public,只需在 enum 前加 pub,如清单 7-10 所示:

文件名:src/lib.rs

mod back_of_house {pub enum Appetizer {Soup,Salad,}
}pub fn eat_at_restaurant() {let order1 = back_of_house::Appetizer::Soup;let order2 = back_of_house::Appetizer::Salad;
}

清单 7-10:将枚举声明为 public 会使所有变体都成为 public
因为 Appetizer 枚举被声明为 public,所以在 eat_at_restaurant 中能够使用 Soup 和 Salad 两个变体。

除非其变体也是公有,否则枚举没什么用;每次都给所有变体现加上 pub 很麻烦,因此默认情况下,enum 的所有变体现均为公有。而 struct 通常即使没有公开其字段也很实用,所以 struct 字段默认全部私有,除非显式标记为 pub。

还有一种涉及 pub 的情况尚未介绍,那就是最后一个关于模组系统特性的 use 关键字。接下来先讲解 use 本身,然后再展示如何结合使用 pub 和 use 。

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

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

相关文章

mac n切换node版本报错Bad CPU type in executable

该node版本仅支持intel芯片,不支持Apple 芯片(M1/M2/M3/M4),所以需要下载Rosetta 2 ,让node可以在搭载 Apple 芯片的 Mac 上运行。 env: node: Bad CPU type in executable /opt/homebrew/bin/n: line 753: /usr/local…

经典算法之美:冒泡排序的优雅实现

经典算法之美:冒泡排序的优雅实现基本概念工作原理介绍具体实现代码实现总结基本概念 冒泡排序是一种简单的排序算法,通过重复比较相邻的元素并交换它们的位置来实现排序。它的名称来源于较小的元素像气泡一样逐渐“浮”到数组的顶端。 工作原理 介绍…

click和touch事件触发顺序 糊里糊涂解决的奇怪bug

问题详情 在嵌入式硬件设备里,测试 “点击input密码框,弹出第三方自带键盘,点击密码框旁的小眼睛,切换输入内容加密状态,键盘收起/弹出状态不变” 的功能逻辑;实际情况却是 “点击键盘或input框之外的任何地…

【0基础PS】Photoshop (PS) 理论知识

目录前言一、Photoshop 核心概念与定位​二、图像基础理论​三、图层理论:PS 的核心工作机制​四、选区与蒙版​五、调色核心理论​六、常用文件格式​学习建议​总结前言 在数字图像编辑领域,Photoshop(简称 PS)无疑是行业标杆级…

数据库 设计 pdm comment列表显示和生成建表sql

按如下步骤 生成见建表语句 comment非空使用comment 生成字段注释, 空的时候使用name 生成字段注释 sql脚本模板编辑 参考 PowerDesigner生成mysql字段comment 注释-腾讯云开发者社区-腾讯云 版本不同这边的设置不同 这个勾打上

嵌入式基础知识复习(C语言)

知识扩展7.28 嵌入式产品特点、开发环境、计算机组成、Linux终端初识1、嵌入式产品。特点:低功耗、根据用户需求定制。硬件:arm处理器。软件:Linux操作系统arm架构:精简指令集、低功耗(移动/嵌入式)。 …

LeetCode Hot 100 寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。算法的时间复杂度应该为 O(log (mn)) 。示例 1:输入:nums1 [1,3], nums2 [2] 输出:2.00000 解释&#x…

监控场景视频质量异常修复:陌讯动态增强算法实战解析

原创声明:本文为原创技术解析,核心技术参数与架构引用自《陌讯技术白皮书》,禁止未经授权转载。一、行业痛点:视频质量异常的连锁难题在安防监控、智慧交通等场景中,视频质量异常已成为 AI 分析的主要瓶颈。据行业报告…

一个简单的mvvm示例与数据双向绑定

这就是一个简单的数据双向绑定的demo,参考即可(cmakelist我没按他的写,但是大差不差) 目录 1.示例demo File: CMakeLists.txt File: main.cpp File: model/physiologymodel.cpp File: viewmodel/physiologyviewmodel.h Fil…

哈希的概念及其应用

哈希的概念及其应用哈希概念常见的哈希其他哈希字符串哈希(算法竞赛常用)字符串哈希OJP3370 【模板】字符串哈希 - 洛谷P10468 兔子与兔子 - 洛谷哈希冲突哈希函数设计原则哈希冲突解决方法—闭散列闭散列的线性探测闭散列的二次探测哈希冲突解决方法—开…

【分布式的个人博客部署】

综合项目-搭建个人博客一、运行环境二、基础配置三、业务需求第一步:准备工作1、配置静态IP2、修改hosts映射3、开启防火墙4、时间同步5、配置免密ssh登录第二步:环境搭建1、Server-web端安装LNMP环境软件2、Server-NFS-DNS端上传博客软件3、Server-NFS-…

蓝桥杯----DS18B20温度传感器

(二)、温度传感器1、One-Wire总线One-Wire总线利用一根线实现双向通信。因此其协议对时序的要求较严格,如应答等时序都有明确的时间要求。基本的时序包括复位及应答时序、写一位时序读一位时序。单总线即只有一根数据线,系统中的数…

科技赋能成长 脑力启迪未来

——西安臻昊科技与秦岭云数智共筑脑科学教育新生态 2025年6月26日,西安臻昊科技(集团)有限责任公司与秦岭云数智(陕西)科技有限公司正式签署脑象评测技术战略合作协议,双方将依托技术互补与资源协同&#…

Docker部署的PostgreSQL慢查询日志配置指南

目录 1. 核心步骤 1.1 修改配置文件 1.2 动态加载配置(无需重启容器) 1.3 验证配置生效 1.3.1 查看参数 1.3.2 执行测试慢查询 2. 高级用法 2.1 使用分析工具 2.2 启用扩展 3. 注意事项 3.1 日志目录权限 3.2 性能影响 配置Docker部署的Pos…

C# 入门教程(四)委托详解

文章目录1、什么是委托2、委托的声明(自定义委托)3、委托的使用3.1 实例:把方法当作参数传给另一个方法3.2 注意:难精通易使用功能强大东西,一旦被滥用则后果非常严重4、委托的高级使用4.1 多播(multicast)委托4.2隐式…

React的基本语法和原理

3. React条件渲染某些情况下,姐妹的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容: 在React中,所有的条件判断和普通的JavaScript代码一致;常见的条件渲染的方式有哪些?方式一&#xff1…

如何在 Gradle 项目中添加依赖?(以添加 AndroidX 版本的 RecyclerView 为例)

1. 确保项目已启用 AndroidX RecyclerView 的现代版本属于 AndroidX 库,需确保项目已启用 AndroidX: 在 gradle.properties 中应有以下配置(通常新建项目默认开启):android.useAndroidXtrue android.enableJetifiert…

深度学习与图像处理 | 基于PaddlePaddle的梯度下降算法实现(线性回归投资预测)

演示基于PaddlePaddle自动求导技术实现梯度下降,简化求解过程。01、梯度下降法梯度下降法是机器学习领域非常重要和具有代表性的算法,它通过迭代计算来逐步寻找目标函数极小值。既然是一种迭代计算方法,那么最重要的就是往哪个方向迭代&#…

负载均衡集群HAproxy

HAProxy 简介HAProxy 是一款高性能的负载均衡器和代理服务器,支持 TCP 和 HTTP 应用。广泛用于高可用性集群,能够有效分发流量到多个后端服务器,确保服务的稳定性和可扩展性。HAProxy 核心功能负载均衡:支持轮询(round…

重生之我在10天内卷赢C++ - DAY 1

坐稳了,我们的C重生之旅现在正式发车!请系好安全带,前方高能,但绝对有趣!🚀 重生之我在10天内卷赢C - DAY 1导师寄语:嘿,未来的编程大神!欢迎来到C的世界。我知道&#x…