门户网站建设与推广方案,线上商城是什么意思,自适应网页设计规范,陕西网站建设培训上一篇#xff1a;06-枚举和模式匹配 当你编写大型程序时#xff0c;组织代码将变得越来越重要。通过对相关功能进行分组并将具有不同功能的代码分开#xff0c;您可以明确在哪里可以找到实现特定功能的代码#xff0c;以及在哪里可以改变功能的工作方式。 到目前为止…上一篇06-枚举和模式匹配 当你编写大型程序时组织代码将变得越来越重要。通过对相关功能进行分组并将具有不同功能的代码分开您可以明确在哪里可以找到实现特定功能的代码以及在哪里可以改变功能的工作方式。 到目前为止我们编写的程序都是一个文件中的一个模块。随着项目的发展你应该把代码分成多个模块然后再分成多个文件。一个软件包可以包含多个二进制包和一个库包。随着软件包的增长你可以将部分代码提取到独立的crates中成为外部依赖。本章将介绍所有这些技术。对于由一系列相互关联、共同发展的软件包组成的大型项目Cargo 提供了工作空间。 我们还将讨论封装实现细节的问题这可以让你在更高层次上重用代码一旦你实现了某个操作其他代码就可以通过它的公共接口调用你的代码而不必知道它是如何实现的。你编写代码的方式定义了哪些部分是公共的供其他代码使用哪些部分是私有的实现细节你保留更改的权利。这是另一种限制你必须记在脑子里的细节数量的方法。 一个相关的概念是作用域编写代码的嵌套上下文有一组被定义为 作用域 的名称。在读取、编写和编译代码时程序员和编译器需要知道特定位置的特定名称是否指代变量、函数、结构体、枚举、模块、常量或其他项目以及该项目的含义。你可以创建作用域改变哪些名称在作用域内哪些在作用域外。在同一个作用域中不能有两个同名的项目可以使用工具来解决名称冲突。 Rust 有许多功能允许你管理代码的组织包括哪些细节是公开的哪些细节是私有的以及程序中每个作用域中的名称。这些功能有时统称为模块系统包括 ①. Packages让您构建、测试和共享crates ②. Crates生成库或可执行文件的模块树 ③. Modules和use 让您控制路径的组织、范围和私密性 ④. Path命名项目如结构、函数或模块的一种方式 在本章中我们将介绍所有这些功能讨论它们如何交互并解释如何使用它们来管理作用域。到本章结束时你应该对模块系统有了扎实的了解并能像专家一样使用作用域
7.1 Packages和Crates 我们要介绍的模块系统的第一个部分是Packages和Crates。 crate 是 Rust 编译器每次考虑的最小代码量。即使你运行的是 rustc 而不是 cargo 并传递一个源代码文件编译器也会将该文件视为一个crate。Crate 可以包含模块模块可以在其他文件中定义这些文件将与 Crate 一起编译。 crate有两种形式二进制、库。二进制crate是可以编译成可执行文件运行的程序例如命令行程序或服务器。每个二进制板crate都必须有一个名为 main 的函数用于定义可执行文件运行时会发生什么。到目前为止我们创建的所有crate都是二进制。 库crate没有 main 功能也不会编译成可执行文件。相反它们定义的功能旨在与多个项目共享。例如我们使用的 rand crate 提供了生成随机数的功能。大多数时候当 Rustaceans 说 crate 时他们指的是库 crate而且他们将 crate 与一般编程概念 library 互换使用。 与C/C代码中的bin和so/a库一样 crate根文件是 Rust 编译器的源文件它构成了crate的根模块。 Packages是由一个或多个 crate 组成的捆绑包提供一系列功能。软件包包含一个 Cargo.toml 文件该文件描述了如何构建这些板块。Cargo 实际上是一个软件包它包含了你用来构建代码的命令行工具的二进制原型。Cargo 软件包还包含二进制原型所依赖的库原型。其他项目可以依赖 Cargo 库原型来使用与 Cargo 命令行工具相同的逻辑。 一个软件包可以包含任意数量的二进制包但最多只能包含一个库包。一个软件包必须至少包含一个原型包无论是库原型包还是二进制原型包。 让我们来看看创建软件包时会发生什么。首先我们输入命令 cargo new
PS D:\rustProj cargo.exe new my-poject Created binary (application) my-poject package
PS D:\rustProj ls .\my-poject\目录: D:\rustProj\my-pojectMode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2024/1/24 14:49 src
-a---- 2024/1/24 14:49 8 .gitignore
-a---- 2024/1/24 14:49 178 Cargo.tomlPS D:\rustProjls .\my-poject\src\目录: D:\rustProj\my-poject\srcMode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2024/1/24 14:49 45 main.rs 运行 cargo new 后我们使用 ls 查看 Cargo 创建了什么。在项目目录中有一个 Cargo.toml 文件为我们提供了一个软件包。还有一个包含 main.rs 的 src 目录。用文本编辑器打开 Cargo.toml注意其中没有提到 src/main.rs。Cargo 遵循的惯例是src/main.rs 是与软件包同名的二进制 crate 的 crate 根目录。同样Cargo 知道如果软件包目录中包含 src/lib.rs那么软件包中就包含一个与软件包同名的库 crate而 src/lib.rs 就是它的 crate 根目录。Cargo 会将 crate 根文件传给 rustc 以便构建库或二进制文件。 这里我们有一个只包含 src/main.rs 的软件包这意味着它只包含一个名为 my-project 的二进制crate。如果软件包包含 src/main.rs 和 src/lib.rs那么它就有两个crate一个二进制和一个库二者都与软件包同名。通过在 src/bin 目录中放置文件软件包可以拥有多个二进制包每个文件都是一个单独的二进制包。
7.2 定义Module以控制范围和隐私 在本节中我们将讨论模块和模块系统的其他部分即允许命名项目的路径、将路径引入作用域的 use 关键字以及使项目公开的 pub 关键字。我们还将讨论 as 关键字、外部包和 glob 操作符。 首先我们将列出一个规则列表以便将来组织代码时参考。然后我们将详细解释每一条规则。
7.2.1 模块清单 在此我们将快速介绍模块、路径、 use 关键字和 pub 关键字在编译器中的工作方式以及大多数开发人员组织代码的方式。我们将在本章中逐一举例说明这些规则但这是一个很好的参考可以提醒我们模块是如何工作的。 ①. 从crate根目录启动从crate根文件开始编译板crates时编译器首先会在create根文件通常是src/lib.rs或src/main.rs中查找要编译的代码。 ②. 声明Modules在 crate 根文件中你可以声明新模块比如你声明了一个 garden 模块文件名是mod garden; 。编译器会在这些地方查找模块代码 Ⅰ. 内联置于大括号内取代mod garden后面的分号 Ⅱ. 在文件 src/garden.rs 中 Ⅲ. 在文件 src/garden/mod.rs 中 ③. 声明子Modules在 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 找到。 ⑤. Public VS Private默认情况下模块内的代码对其父模块是私有的。若要将模块变为公共模块请使用 pub mod 而不是 mod 进行声明。若要将公共模块中的项目也设为公共模块请在其声明前使用 pub 。 ⑥. use关键字在作用域中 use 关键字为项目创建快捷方式以减少长路径的重复。在任何可以引用 crate::garden::vegetables::Asparagus 的作用域中您都可以用 use crate::garden::vegetables::Asparagus; 创建快捷方式此后您只需编写 Asparagus 就可以在作用域中使用该类型。 类似于C/C中的using关键字 在此我们创建一个名为 backyard 的二进制crate来说明这些规则。crate 的目录也命名为 backyard 其中包含这些文件和目录 本例中的 crate 根文件是 src/main.rs其中包含
use crate::garden::vegetables::Asparagus;pub mod garden;fn main() {let plant Asparagus {};println!(Hello, world!);
}pub mod garden 告诉编译器包含garden并在 src/garden.rs 中找到的代码
pub mod vegetables; 这里 pub mod vegetables 意味着 src/garden/vegetables.rs 中的代码也包括在内。这些代码是
#[derive(Debug)]
pub struct Asparagus {}现在让我们来详细了解这些规则并在实际操作中加以演示
7.2.2 在模块中分组相关代码 模块让我们可以在一个crate中组织代码以提高可读性并方便重复使用。模块还允许我们控制项目的私密性因为模块内的代码默认为私有。私有项目是内部实现细节不对外公开。我们可以选择将模块和模块中的项目公开这样就可以让外部代码使用和依赖它们。 例如让我们编写一个提供餐厅功能的库。我们将定义函数的签名但函数体留空以便集中精力组织代码而不是实现餐厅的功能。 在餐饮业餐厅的某些部分被称为前厅另一些部分被称为后厨。前厅是顾客所在的地方包括主人为顾客安排座位、服务员接受点单和付款以及调酒师调制饮料的地方。后厨是厨师和厨工在厨房工作、洗碗工进行清洁以及经理进行行政工作的地方。 为了以这种方式构建我们的 crate我们可以将其函数组织成嵌套模块。运行 cargo new restaurant --lib 创建一个名为 restaurant 的新库然后在 src/lib.rs 中输入如下代码定义一些模块和函数签名。下面是前台部分
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() {}}
}(清单 7-1 front_of_house 模块包含其他模块这些模块又包含函数) 在定义模块时我们使用 mod 关键字然后是模块名称本例中为 front_of_house 。模块的正文放在大括号内。在模块内部我们可以放置其他模块就像本例中的模块 hosting 和 serving 一样。模块还可以放置其他项目的定义如结构体、枚举、常量、特质以及清单 7-1 中的函数。 通过使用模块我们可以将相关的定义组合在一起并命名它们的关联原因。使用此代码的程序员可以根据分组来浏览代码而不必通读所有定义从而更容易找到与之相关的定义。程序员在为代码添加新功能时也会知道该把代码放在哪里从而使程序井井有条。 前面我们提到src/main.rs 和 src/lib.rs 被称为 crate 根。之所以叫这两个文件的根是因为这两个文件的内容构成了一个名为 crate 的模块位于crate模块结构即模块树的根部。 下图显示了上述代码结构的模块树。 (清单 7-2清单 7-1 中代码的模块树) 这棵树显示了一些模块是如何相互嵌套的例如 hosting 嵌套在 front_of_house 中。这棵树还显示有些模块是同级模块即它们定义在同一个模块中 hosting 和 serving 是定义在 front_of_house 中的同级模块。如果模块 A 包含在模块 B 中我们就说模块 A 是模块 B 的子模块而模块 B 是模块 A 的父模块。请注意整个模块树的根是名为 crate 的隐式模块。 模块树可能会让你联想到Linux系统上的文件系统目录树这是一个非常恰当的比较就像文件系统中的目录一样你可以使用模块来组织代码。就像目录中的文件一样我们需要找到模块的方法。
7.3 模块树中的项目引用路径 为了告诉 Rust 在模块树中的哪个位置可以找到某个项目我们使用了路径就像我们在文件系统中使用路径一样。要调用一个函数我们需要知道它的路径。 路径有两种形式 ①. 绝对路径是从crate根开始的完整路径对于外部crate的代码绝对路径从crate名称开始而对于当前crate的代码绝对路径从字面 crate 开始。 ②. 相对路径从当前模块开始使用 self 、 super 或当前模块中的标识符。 绝对路径和相对路径后面都有一个或多个标识符用双冒号 ( :: ) 分隔。 回到restaurant代码假设我们要调用 add_to_waitlist 函数。这等同于问 add_to_waitlist 函数的路径是什么 我们将展示两种从 crate root 中定义的新函数 eat_at_restaurant 调用 add_to_waitlist 函数的方法。这些路径都是正确的但还存在另一个问题会导致本示例无法按原样编译。稍后我们将解释原因。 eat_at_restaurant 函数是我们库 crate 的公共 API 的一部分因此我们用 pub 关键字标记它。
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() {}}
}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 名称从板块根目录启动就像在 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看看为什么还不能编译我们得到如下的错误信息
cargo.exe buildCompiling restaurant v0.1.0 (D:\rustProj\restaurant)
error[E0603]: module hosting is private-- src\lib.rs:16:28|
16 | 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:19:21|
19 | 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 关键字将某项公开从而向外部祖先模块公开子模块的内部代码。
7.3.1 使用 pub 关键字公开路径 让我们回到上述错误该错误告诉我们 hosting 模块是私有的。我们希望父模块中的 eat_at_restaurant 函数能访问子模块中的 add_to_waitlist 函数因此我们用 pub 关键字标记 hosting 模块如下代码所示
mod front_of_house {pub mod hosting {fn add_to_waitlist() {}fn seat_at_table() {}}mod serving {fn take_order() {}fn server_order() {}fn take_payment() {}}
}pub fn eat_at_restaurant() {// Absolute pathcrate::front_of_house::hosting::add_to_waitlist();// Relative pathfront_of_house::hosting::add_to_waitlist();
}(清单 7-5将 hosting 模块声明为 pub 以便在以下情况下使用它 eat_at_restaurant) 不幸的是如清单 7-6 所示清单 7-5 中的代码仍会导致错误。
PS D:\rustProj\restaurant cargo.exe buildCompiling restaurant v0.1.0 (D:\rustProj\restaurant)
error[E0603]: function add_to_waitlist is private-- src\lib.rs:16:37|
16 | 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:19:30|
19 | 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 关键字只能让其祖先模块中的代码引用它而不能访问其内部代码。因为模块是一个容器所以只将模块公开并不能做什么我们需要进一步选择将模块中的一个或多个项也公开。 错误表明 add_to_waitlist 函数是私有的。隐私规则适用于结构体、枚举、函数和方法以及模块。 我们还可以在 add_to_waitlist 函数的定义前添加 pub 关键字使其成为公共函数如下所示
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}fn seat_at_table() {}}mod serving {fn take_order() {}fn server_order() {}fn take_payment() {}}
}pub fn eat_at_restaurant() {// Absolute pathcrate::front_of_house::hosting::add_to_waitlist();// Relative pathfront_of_house::hosting::add_to_waitlist();
}(清单 7-7在 mod hosting 和 fn add_to_waitlist 中添加 pub 关键字后我们就可以调用来自 eat_at_restaurant) 现在代码可以编译了要了解为什么添加 pub 关键字能让我们在 add_to_waitlist 中使用这些路径而不违反隐私规则让我们来看看绝对路径和相对路径。 在绝对路径中我们从 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 因此路径的其余部分都有效这个函数调用也就有效了 如果你计划共享你的库板块以便其他项目可以使用你的代码那么你的公共 API 就是你与你的crate用户之间的契约它决定了用户如何与你的代码交互。要管理公共 API 的变更使人们更容易依赖你的 crate需要考虑很多因素。 包含二进制文件和库的软件包的最佳实践 我们提到过软件包可以同时包含 src/main.rs 二进制和 src/lib.rs 库默认情况下这两个crate都使用软件包名称。通常情况下同时包含库和二进制源码包的软件包会在二进制源码包中加入足够的代码以启动调用库源码包代码的可执行文件。这样其他项目就能从软件包提供的大部分功能中获益因为库板块的代码可以共享。 模块树应在 src/lib.rs 中定义。然后任何公共项目都可以通过以软件包名称开头的路径在二进制crate中使用。二进制crate将成为库crate的用户就像完全外部板块使用库一样它只能使用公共 API。这可以帮助你设计一个好的应用程序接口你不仅是作者也是客户 7.3.2 用super启动相对路径 我们可以通过在路径开头使用 super 来构建从父模块而非当前模块或crate根开始的相对路径。这就像使用 .. 语法启动文件系统路径一样。使用 super 我们就可以引用我们知道是在父模块中的项目当模块与父模块关系密切但父模块有一天可能会被移到模块树的其他地方时这可以让重新排列模块树变得更容易。 请看如下代码该代码模拟了厨师修改错误订单并亲自将其送到顾客手中的情况。 back_of_house 模块中定义的函数 fix_incorrect_order 调用父模块中定义的函数 deliver_order 方法是指定从 super 开始的 deliver_order 的路径
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 这样如果这些代码被移动到不同的模块我们将来更新代码的地方就会减少。
7.3.3 公有结构体和枚举 我们也可以使用 pub 将结构体和枚举指定为公用但在结构体和枚举中使用 pub 还有一些额外的细节。如果我们在结构体定义前使用 pub 我们就会将结构体设为公共结构体但结构体的字段仍然是私有的。我们可以根据具体情况决定是否公开每个字段。在下面代码示例中我们定义了一个公共的 back_of_house::Breakfast 结构它有一个公共的 toast 字段但有一个私有的 seasonal_fruit 字段。这模拟了餐厅的情况顾客可以选择配餐的面包类型但厨师会根据当季和库存情况决定配餐的水果。现有的水果变化很快所以顾客无法选择水果甚至无法看到他们会吃到哪种水果。
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() {// Order a breakfast in the summer with Rye toastlet mut meal back_of_house::Breakfast::summer(Rye);// Change our mind about what bread wed likemeal.toast String::from(Wheat);println!(Id like {} toast please, meal.toast);// The next line wont compile if we uncomment it; were not allowed// to see or modify the seasonal fruit that comes with the meal// meal.seasonal_fruit String::from(blueberries);
}
(清单 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 如下代码所示
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将枚举指定为公共枚举会使其所有变体都成为公共枚举) 由于我们将 Appetizer 枚举公开因此可以在 eat_at_restaurant 中使用 Soup 和 Salad 变体。 除非枚举的变体是公开的否则枚举的用处就不大如果每次都要用 pub 来注解所有枚举变体那就太烦人了所以枚举变体的默认值是公开的。结构体通常在字段不公开的情况下也很有用因此结构体字段遵循一般规则即除非使用 pub 注释否则默认情况下所有字段都是私有的。 还有一种涉及 pub 的情况我们没有涉及那就是我们最后一个模块系统功能 use 关键字。我们将首先介绍 use 本身然后演示如何将 pub 和 use 结合起来。
7.4 使用 use 关键字将路径纳入范围 必须写出调用函数的路径可能会让人感觉不便和重复。在上面代码中无论我们选择的是 add_to_waitlist 函数的绝对路径还是相对路径每次要调用 add_to_waitlist 时都必须指定 front_of_house 和 hosting 。幸运的是有一种方法可以简化这一过程我们可以使用 use 关键字创建一次路径快捷方式然后在作用域中的其他地方使用较短的名称。 下面代码中我们将 crate::front_of_house::hosting 模块引入 eat_at_restaurant 函数的作用域这样我们只需指定 hosting::add_to_waitlist 就可以调用 eat_at_restaurant 中的 add_to_waitlist 函数。
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();
}
(清单 7-11使用 use将模块加入作用域) 在作用域中添加 use 和路径类似于在文件系统中创建符号链接。在crate根目录中添加 use crate::front_of_house::hosting 后 hosting 就成为该作用域中的有效名称就像在crate根目录中定义了 hosting 模块一样。使用 use 进入作用域的路径也会像其他路径一样检查隐私。 请注意 use 只为 use 出现的特定作用域创建快捷方式。下面代码示例将 eat_at_restaurant 函数移入一个名为 customer 的新子模块该子模块的作用域与 use 语句不同因此函数体将无法编译
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}use crate::front_of_house::hosting;mod customer {pub fn eat_at_restaurant() {hosting::add_to_waitlist();}
}
(清单 7-12 use 语句只适用于它所在的作用域) 编译器错误显示快捷方式不再适用于 customer 模块
cargo.exe buildCompiling restaurant v0.1.0 (D:\rustProj\restaurant)
error[E0433]: failed to resolve: use of undeclared crate or module hosting-- src\lib.rs:11:9|
11 | hosting::add_to_waitlist();| ^^^^^^^ use of undeclared crate or module hosting|
help: consider importing this module through its public re-export|
10 use crate::hosting;|warning: unused import: crate::front_of_house::hosting-- src\lib.rs:7:5|
7 | use crate::front_of_house::hosting;| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| note: #[warn(unused_imports)] on by defaultFor more information about this error, try rustc --explain E0433.
warning: restaurant (lib) generated 1 warning
error: could not compile restaurant (lib) due to previous error; 1 warning emitted 注意还有一个警告即 use 在其作用域内已不再使用要解决这个问题请将 use 也移到 customer 模块中或在子 customer 模块中用 super::hosting 引用父模块中的快捷方式。
7.4.1 创建惯用的使用路径 在上面代码中您可能想知道为什么我们指定了 use crate::front_of_house::hosting 然后在 eat_at_restaurant 中调用 hosting::add_to_waitlist 而不是像如下代码示例中那样为了达到同样的效果一直指定 use 路径到 add_to_waitlist 函数。
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}use crate::front_of_house::hosting::add_to_waitlist;pub fn eat_at_restaurant() {add_to_waitlist();
}
(清单 7-13使用 use 将 add_to_waitlist 函数引入作用域这不符合规范) 尽管都完成了相同的任务但之前的代码是通过 use 将函数引入作用域的惯用方式通过 use 将函数的父模块引入作用域意味着我们必须在调用函数时指定父模块。在调用函数时指定父模块可以清楚地表明函数不是本地定义的同时还能最大限度地减少完整路径的重复。清单 7-13 中的代码不清楚 add_to_waitlist 的定义位置。 一方面当使用 use 引入结构体、枚举和其他项目时指定完整路径是一种习惯做法。下列代码展示了将标准库的 HashMap 结构引入二进制 crate 的作用域的惯用方法。
use std::collections::HashMap;fn main() {let mut map HashMap::new();map.insert(1, 2);
}
(清单 7-14以惯用方式将 HashMap 引入作用域) 这个惯例的背后并没有很强的理由它只是一种约定俗成的习惯人们已经习惯了用这种方式来阅读和编写 Rust 代码。 如果我们使用 use 语句将两个同名的项目引入作用域那么这个惯例就是个例外因为 Rust 不允许这样做。下列代码展示了如何将两个具有相同名称但不同父模块的 Result 类型引入作用域以及如何引用它们。
use std::fmt;
use std::io;fn function1() - fmt::Result {// --snip--
}fn function2() - io::Result() {// --snip--
}
(清单 7-15将两个同名类型带入同一作用域需要使用它们的父模块) 如您所见使用父模块可以区分两个 Result 类型。如果我们指定的是 use std::fmt::Result 和 use std::io::Result 那么在同一作用域中就会有两个 Result 类型Rust 就不知道我们在使用 Result 时指的是哪一个了。 这个就是一个命名空间的问题要保持函数命名的独一性 7.4.2 使用 as 关键字提供新名称 对于将两个同名类型带入 use 的同一作用域的问题还有另一种解决方案在路径之后我们可以指定 as 以及该类型的新本地名称或别名。清单 7-16 展示了清单 7-15 中代码的另一种写法即使用 as 重命名两个 Result 类型中的一个。
use std::fmt::Result;
use std::io::Result as IoResult;fn function1() - Result {// --snip--
}fn function2() - IoResult() {// --snip--
}use std::fmt::Result;
use std::io::Result as IoResult;fn function1() - Result {// --snip--
}fn function2() - IoResult() {// --snip--
}
(清单 7-16当使用 as 关键字将一个类型带入作用域时对其进行重命名) 在第二个 use 语句中我们为 std::io::Result 类型选择了新名称 IoResult 这样就不会与我们也已纳入作用域的 std::fmt 中的 Result 冲突。 类似于C中的using操作 namespace MyNamespace { class MyClass {}; } // Creating an alias for MyNamespace::MyClassusing MyClassAlias MyNamespace::MyClass; // Now you can use MyClassAlias as a shorthand for MyNamespace::MyClass MyClassAlias obj; 7.4.3 使用pub重新导出名称 当我们使用 use 关键字将名称引入作用域时新作用域中的名称是私有的。为了使调用我们代码的代码能够引用该名称就好像它是在该代码的作用域中定义的一样我们可以将 pub 和 use 结合起来。这种技术被称为再导出因为我们在将一个项目引入作用域的同时也让其他人可以将该项目引入他们的作用域。 下列示例代码根模块中的 use 更改为 pub use 。
mod front_of_house {pub mod hosting {pub fn add_to_waitlist() {}}
}pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();
}
(清单 7-17在一个新的作用域中使用 pub use) 在这一变更之前外部代码必须使用 restaurant::front_of_house::hosting::add_to_waitlist() 这一路径来调用 add_to_waitlist 函数。现在 pub use 已从根模块中重新导出了 hosting 模块外部代码现在可以使用路径 restaurant::hosting::add_to_waitlist() 代替。 当代码的内部结构与调用代码的程序员对领域的理解不同时重新导出就非常有用。例如在这个餐馆隐喻中经营餐馆的人考虑的是 前厅 和 后厨。但光顾餐厅的顾客可能不会用这些术语来思考餐厅的各个部分。有了 pub use 我们就可以用一种结构编写代码但暴露不同的结构。这样做可以使我们的程序库井井有条便于程序员使用程序库也便于程序员调用程序库。
7.4.4 使用外部Packages 在第 2 章中我们编写了一个竞猜游戏项目该项目使用名为 rand 的外部软件包来获取随机数。为了在项目中使用 rand 我们在 Cargo.toml 中添加了这一行
rand 0.8.5 在 Cargo.toml 中将 rand 添加为依赖关系后Cargo 就会从 crates.io 下载 rand 软件包和任何依赖关系并将 rand 提供给我们的项目。 然后为了将 rand 定义引入包的作用域我们添加了一行以板条箱名称 rand 开头的 use 行并列出了要引入作用域的项目。回想一下 生成随机数 一节中我们将 Rng 特质引入了作用域并调用了 rand::thread_rng 函数
use rand::Rng;fn main() {let secret_number rand::thread_rng().gen_range(1..100);
} Rust 社区成员在 crates.io 上提供了许多软件包将其中任何一个拉入你的软件包都需要这些相同的步骤在你的软件包的 Cargo.toml 文件中列出它们并使用 use 将项目从它们的crate拉入作用域。 请注意 std 标准库也是我们软件包外部的一个 crate。因为标准库是随 Rust 语言一起提供的所以我们不需要修改 Cargo.toml 以包含 std 。但我们确实需要用 use 来引用它以便将其中的项目引入我们软件包的范围。例如在 HashMap 中我们可以使用这一行
use std::collections::HashMap; 这是一个以 std 标准库板块的名称开头的绝对路径。
7.4.5 使用嵌套路径清理大型 use 列表 如果我们使用的是定义在同一crate或同一module中的多个项目将每个项目单独列一行会占用文件中大量的垂直空间。例如猜谜游戏中使用的这两条 use 语句将 std 中的项目引入作用域
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--相反我们可以使用嵌套路径在一行中将相同的项目引入作用域。具体做法是指定路径的共同部分后面加两个冒号然后在路径不同部分的列表周围加上大括号如下代码所示
// --snip--
use std::{cmp::Ordering, io};
// --snip--(清单 7-18指定嵌套路径将具有相同前缀的多个项目纳入作用域) 在大型程序中使用嵌套路径将同一板条箱或模块中的许多项目引入作用域可以大大减少所需的单独 use 语句的数量 我们可以在路径的任何层级使用嵌套路径这在组合共享子路径的两个 use 语句时非常有用。例如下列代码显示了两个 use 语句一个将 std::io 带入作用域另一个将 std::io::Write 带入作用域。
use std::io;
use std::io::Write;
(清单 7-19两个 use 语句其中一个是另一个的子路径) 这两条路径的共同部分是 std::io 这就是完整的第一条路径。要将这两条路径合并为一条 use 语句我们可以在嵌套路径中使用 self 如下所示
use std::io::{self, Write};
(清单 7-20将清单 7-19 中的路径合并为一条 use 语句) 这一行将 std::io 和 std::io::Write 纳入范围。
7.4.6 Glob 操作 如果我们想将某个路径中定义的所有公共项都纳入作用域可以指定该路径然后使用 * glob 操作符
use std::collections::*;use 语句将 std::collections 中定义的所有公共项引入当前作用域。使用 glob 操作符时要小心Glob 会使您更难分辨哪些名称在作用域中以及程序中使用的名称是在哪里定义的。 测试时glob 操作符通常用于将所有被测试的内容引入 tests 模块。glob 操作符有时也作为前奏模式的一部分使用。
7.5 将模块分成不同的文件 到目前为止本章的所有示例都在一个文件中定义了多个模块。当模块变大时你可能想把它们的定义移到一个单独的文件中这样代码更容易浏览。 例如让我们从清单 7-17 中包含多个餐厅模块的代码开始。我们将把模块提取到文件中而不是把所有模块都定义在 crate 根文件中。在本例中crate 根文件是 src/lib.rs但这一过程也适用于二进制 crate其 crate 根文件是 src/main.rs。 首先我们将 front_of_house 模块提取到自己的文件中。删除 front_of_house 模块大括号内的代码只留下 mod front_of_house; 声明这样 src/lib.rs 文件就包含了清单 7-21 所示的代码。请注意在创建清单 7-22 中的 src/front_of_house.rs 文件之前这段代码不会被编译。
mod front_of_house;pub use crate::front_of_house::hosting;pub fn eat_at_restaurant() {hosting::add_to_waitlist();
}
(清单 7-21声明 front_of_house 模块其主体将放在 src/front_of_house.rs 中) 接下来将大括号中的代码放入一个名为 src/front_of_house.rs 的新文件中如下代码所示。编译器之所以知道要在这个文件中查找是因为它在 crate 根中找到了模块声明其名称为 front_of_house 。
pub mod hosting {pub fn add_to_waitlist() {}
}
(清单 7-22src/front_of_house.rs 中 front_of_house 模块内的定义) 请注意您只需在模块树中使用 mod 声明加载一次文件。一旦编译器知道该文件是项目的一部分并且知道代码在模块树中的位置因为你把 mod 语句放在了哪里项目中的其他文件就应该使用 在模块树中引用项目的路径 一节中介绍的声明文件的路径来引用加载文件的代码。换句话说 mod 并不是你在其他编程语言中看到的 包含 操作。 接下来我们将把 hosting 模块提取到它自己的文件中。这个过程有点不同因为 hosting 是 front_of_house 的子模块而不是根模块。我们将把 hosting 的文件放到一个新的目录中该目录将以其在模块树中的祖先命名本例中为 src/front_of_house/。 开始移动 hosting 时我们修改 src/front_of_house.rs使其仅包含 hosting 模块的声明
pub mod hosting; 然后我们创建一个 src/front_of_house 目录和一个 hosting.rs 文件其中包含 hosting 模块中的定义
pub fn add_to_waitlist() {} 如果我们把 hosting.rs 放在 src 目录下编译器就会认为 hosting.rs 的代码是在 hosting 模块中而不是作为 front_of_house 模块的子模块声明在 crate 根目录下。编译器会根据哪些文件检查哪些模块的代码这意味着目录和文件更接近模块树。 备用文件路径 到目前为止我们已经介绍了 Rust 编译器使用的最惯用的文件路径但 Rust 还支持一种旧式的文件路径。对于在 crate 根目录中声明的名为 front_of_house 的模块编译器会在以下目录中查找该模块的代码 src/front_of_house.rs 我们所涉及的内容 src/front_of_house/mod.rs旧式仍支持路径 如果一个名为 hosting 的模块是 front_of_house 的子模块编译器会在以下位置查找该模块的代码 src/front_of_house/hosting.rs 我们涉及的内容 src/front_of_house/hosting/mod.rs旧式仍支持路径 如果在同一模块中同时使用两种样式编译器会出错。对同一项目中的不同模块混合使用两种样式是允许的但可能会让浏览项目的人感到困惑。 使用名为 mod.rs 的文件格式的主要缺点是你的项目最终会有许多名为 mod.rs 的文件当你同时在编辑器中打开这些文件时可能会造成混乱。 我们将每个模块的代码都移到了单独的文件中模块树保持不变。 eat_at_restaurant 中的函数调用无需任何修改即可运行即使定义存在于不同的文件中。使用这种技术可以随着模块规模的扩大将它们移到新的文件中。 请注意src/lib.rs 中的 pub use crate::front_of_house::hosting 语句也没有改变 use 也不会对作为 crate 一部分编译的文件产生任何影响。 mod 关键字声明了模块Rust 会在与模块同名的文件中查找该模块的代码。 下一篇 08-常用集合(容器) 文章转载自: http://www.morning.xpgwz.cn.gov.cn.xpgwz.cn http://www.morning.zknjy.cn.gov.cn.zknjy.cn http://www.morning.fyglg.cn.gov.cn.fyglg.cn http://www.morning.cttgj.cn.gov.cn.cttgj.cn http://www.morning.bslkt.cn.gov.cn.bslkt.cn http://www.morning.hrqfl.cn.gov.cn.hrqfl.cn http://www.morning.pybqq.cn.gov.cn.pybqq.cn http://www.morning.plpqf.cn.gov.cn.plpqf.cn http://www.morning.swzpx.cn.gov.cn.swzpx.cn http://www.morning.zmqb.cn.gov.cn.zmqb.cn http://www.morning.rglp.cn.gov.cn.rglp.cn http://www.morning.rhzzf.cn.gov.cn.rhzzf.cn http://www.morning.xjpnq.cn.gov.cn.xjpnq.cn http://www.morning.cjsrg.cn.gov.cn.cjsrg.cn http://www.morning.plfrk.cn.gov.cn.plfrk.cn http://www.morning.rhkq.cn.gov.cn.rhkq.cn http://www.morning.bmsqq.cn.gov.cn.bmsqq.cn http://www.morning.dtzsm.cn.gov.cn.dtzsm.cn http://www.morning.ttdbr.cn.gov.cn.ttdbr.cn http://www.morning.pccqr.cn.gov.cn.pccqr.cn http://www.morning.wrfk.cn.gov.cn.wrfk.cn http://www.morning.mxhys.cn.gov.cn.mxhys.cn http://www.morning.mhmcr.cn.gov.cn.mhmcr.cn http://www.morning.wkknm.cn.gov.cn.wkknm.cn http://www.morning.cykqb.cn.gov.cn.cykqb.cn http://www.morning.ltqzq.cn.gov.cn.ltqzq.cn http://www.morning.hmxb.cn.gov.cn.hmxb.cn http://www.morning.tjsxx.cn.gov.cn.tjsxx.cn http://www.morning.btmwd.cn.gov.cn.btmwd.cn http://www.morning.xprzq.cn.gov.cn.xprzq.cn http://www.morning.fpngg.cn.gov.cn.fpngg.cn http://www.morning.npbgj.cn.gov.cn.npbgj.cn http://www.morning.qklff.cn.gov.cn.qklff.cn http://www.morning.ljxps.cn.gov.cn.ljxps.cn http://www.morning.cprls.cn.gov.cn.cprls.cn http://www.morning.rjjys.cn.gov.cn.rjjys.cn http://www.morning.gfrtg.com.gov.cn.gfrtg.com http://www.morning.pwggd.cn.gov.cn.pwggd.cn http://www.morning.wmgjq.cn.gov.cn.wmgjq.cn http://www.morning.tqfnf.cn.gov.cn.tqfnf.cn http://www.morning.rckmz.cn.gov.cn.rckmz.cn http://www.morning.zqmdn.cn.gov.cn.zqmdn.cn http://www.morning.kzhxy.cn.gov.cn.kzhxy.cn http://www.morning.plxnn.cn.gov.cn.plxnn.cn http://www.morning.ckzjl.cn.gov.cn.ckzjl.cn http://www.morning.rsfp.cn.gov.cn.rsfp.cn http://www.morning.dhnqt.cn.gov.cn.dhnqt.cn http://www.morning.qrwdg.cn.gov.cn.qrwdg.cn http://www.morning.cctgww.cn.gov.cn.cctgww.cn http://www.morning.rqqct.cn.gov.cn.rqqct.cn http://www.morning.wbdm.cn.gov.cn.wbdm.cn http://www.morning.zwndt.cn.gov.cn.zwndt.cn http://www.morning.kfwqd.cn.gov.cn.kfwqd.cn http://www.morning.xfxlr.cn.gov.cn.xfxlr.cn http://www.morning.yqqgp.cn.gov.cn.yqqgp.cn http://www.morning.gmnmh.cn.gov.cn.gmnmh.cn http://www.morning.pxwjp.cn.gov.cn.pxwjp.cn http://www.morning.wkmpx.cn.gov.cn.wkmpx.cn http://www.morning.pqcsx.cn.gov.cn.pqcsx.cn http://www.morning.gmrxh.cn.gov.cn.gmrxh.cn http://www.morning.wnywk.cn.gov.cn.wnywk.cn http://www.morning.wynnb.cn.gov.cn.wynnb.cn http://www.morning.fwgnq.cn.gov.cn.fwgnq.cn http://www.morning.bgrsr.cn.gov.cn.bgrsr.cn http://www.morning.qwbht.cn.gov.cn.qwbht.cn http://www.morning.qfmns.cn.gov.cn.qfmns.cn http://www.morning.wnywk.cn.gov.cn.wnywk.cn http://www.morning.psgbk.cn.gov.cn.psgbk.cn http://www.morning.rlhgx.cn.gov.cn.rlhgx.cn http://www.morning.rfxyk.cn.gov.cn.rfxyk.cn http://www.morning.rftk.cn.gov.cn.rftk.cn http://www.morning.wngpq.cn.gov.cn.wngpq.cn http://www.morning.jrpmf.cn.gov.cn.jrpmf.cn http://www.morning.zqdzg.cn.gov.cn.zqdzg.cn http://www.morning.yqmmh.cn.gov.cn.yqmmh.cn http://www.morning.hrgxk.cn.gov.cn.hrgxk.cn http://www.morning.dyzbt.cn.gov.cn.dyzbt.cn http://www.morning.rjrlx.cn.gov.cn.rjrlx.cn http://www.morning.mdpkf.cn.gov.cn.mdpkf.cn http://www.morning.sjqml.cn.gov.cn.sjqml.cn