mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 00:43:59 +08:00
commit
d3c7c598cb
@ -4,12 +4,12 @@
|
||||
> <br>
|
||||
> commit b707dc664960f0ffc495c373900d6b13e434927d
|
||||
|
||||
在你刚开始编写 Rust 程序时,代码可能仅仅位于 `main` 函数里。随着代码数量的增长,最终你会将功能移动到其他函数中,为了复用也为了更好的组织。通过将代码分隔成更小的块,每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢?Rust 有一个模块系统可以有组织的复用代码。
|
||||
在你刚开始编写 Rust 程序时,代码可能仅仅位于 `main` 函数里。随着代码数量的增长,为了复用和更好地组织代码,最终你会将功能移动到其他函数中。通过将代码分隔成更小的块,每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢?Rust 有一个模块系统可以有组织地复用代码。
|
||||
|
||||
就跟你将代码行提取到一个函数中一样,也可以将函数(和其他类似结构体和枚举的代码)提取到不同模块中。**模块**(*module*)是一个包含函数或类型定义的命名空间,你可以选择这些定义是能(公有)还是不能(私有)在其模块外可见。这是一个模块如何工作的概括:
|
||||
就跟你将代码行提取到一个函数中一样,也可以将函数(和其他类似结构体和枚举的代码)提取到不同模块中。**模块**(*module*)是一个包含函数或类型定义的命名空间,你可以选择这些定义能(公有)或不能(私有)在其模块外可见。下面是一个模块如何工作的梗概:
|
||||
|
||||
* 使用 `mod` 关键字声明新模块。此模块的代码要么直接位于声明之后的大括号中,要么位于另一个文件。
|
||||
* 函数、类型、常量和模块默认都是私有的。可以使用 `pub` 关键字将其变成公有并在命名空间之外可见。
|
||||
* `use` 关键字引入模块、或模块中的定义到作用域中以便于引用他们。
|
||||
* `use` 关键字将模块或模块中的定义引入到作用域中以便于引用它们。
|
||||
|
||||
我们会逐一了解这每一部分并学习如何将他们结合在一起。
|
||||
我们会逐一了解这每一部分并学习如何将它们结合在一起。
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit c6a9e77a1b1ed367e0a6d5dcd222589ad392a8ac
|
||||
|
||||
我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章猜猜看游戏中作为依赖使用的 `rand` 就是这样的 crate。
|
||||
我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过这次不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章猜猜看游戏中作为依赖使用的 `rand` 就是这样的 crate。
|
||||
|
||||
我们将创建一个提供一些通用网络功能的项目的骨架结构;我们将专注于模块和函数的组织,而不担心函数体中的具体代码。这个项目叫做 `communicator`。Cargo 默认会创建一个库 crate 除非指定其他项目类型,所以如果不像一直以来那样加入 `--bin` 参数则项目将会是一个库:
|
||||
|
||||
@ -22,11 +22,12 @@ $ cd communicator
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用 `--bin` 参数那样创建一个 “Hello, world!” 二进制项目。在本章之后的 “使用 `super` 访问父模块” 部分会介绍 `#[]` 和 `mod tests` 语法,目前只需确保他们位于 *src/lib.rs* 底部即可。
|
||||
Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用 `--bin` 参数那样创建一个 “Hello, world!” 二进制项目。在本章之后的 “使用 `super` 访问父模块” 部分会介绍 `#[]` 和 `mod tests` 语法,目前只需确保它们位于 *src/lib.rs* 底部即可。
|
||||
|
||||
因为没有 *src/main.rs* 文件,所以没有可供 Cargo 的 `cargo run` 执行的东西。因此,我们将使用 `cargo build` 命令只是编译库 crate 的代码。
|
||||
|
||||
@ -65,7 +66,7 @@ mod client {
|
||||
|
||||
<span class="caption">示例 7-1:`network` 模块和 `client` 一同定义于 *src/lib.rs*</span>
|
||||
|
||||
现在我们有了 `network::connect` 函数和 `client::connect` 函数。他们可能有着完全不同的功能,同时他们也不会彼此冲突,因为他们位于不同的模块。
|
||||
现在我们有了 `network::connect` 函数和 `client::connect` 函数。它们可能有着完全不同的功能,同时它们也不会彼此冲突,因为它们位于不同的模块。
|
||||
|
||||
在这个例子中,因为我们构建的是一个库,作为库入口点的文件是 *src/lib.rs*。然而,对于创建模块来说,*src/lib.rs* 并没有什么特殊意义。也可以在二进制 crate 的 *src/main.rs* 中创建模块,正如在库 crate 的 *src/lib.rs* 创建模块一样。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。如何选择组织代码依赖于如何考虑代码不同部分之间的关系。例如,对于库的用户来说,`client` 模块和它的函数 `connect` 可能放在 `network` 命名空间里显得更有道理,如示例 7-2 所示:
|
||||
|
||||
@ -85,7 +86,7 @@ mod network {
|
||||
|
||||
<span class="caption">示例 7-2:将 `client` 模块移动到 `network` 模块中</span>
|
||||
|
||||
在 *src/lib.rs* 文件中,将现有的 `mod network` 和 `mod client` 的定义替换为示例 7-2 中的定义,这里将 `client` 模块作为 `network` 的一个内部模块。现在我们有了 `network::connect` 和 `network::client::connect` 函数:同样的,这两个 `connect` 函数也不相冲突,因为他们在不同的命名空间中。
|
||||
在 *src/lib.rs* 文件中,将现有的 `mod network` 和 `mod client` 的定义替换为示例 7-2 中的定义,这里将 `client` 模块作为 `network` 的一个内部模块。现在我们有了 `network::connect` 和 `network::client::connect` 函数:同样的,这两个 `connect` 函数也不相冲突,因为它们在不同的命名空间中。
|
||||
|
||||
这样,模块之间形成了一个层次结构。*src/lib.rs* 的内容位于最顶层,而其子模块位于较低的层次。如下是示例 7-1 中的例子以层次的方式考虑的结构:
|
||||
|
||||
@ -103,7 +104,7 @@ communicator
|
||||
└── client
|
||||
```
|
||||
|
||||
可以看到示例 7-2 中,`client` 是 `network` 的子模块,而不是它的同级模块。更为复杂的项目可以有很多的模块,所以他们需要符合逻辑地组合在一起以便记录他们。在项目中 “符合逻辑” 的意义全凭你的理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块,总有一个会是你会喜欢的结构。
|
||||
可以看到示例 7-2 中,`client` 是 `network` 的子模块,而不是它的同级模块。更为复杂的项目可以有很多的模块,所以它们需要符合逻辑地组合在一起以便记录它们。在项目中 “符合逻辑” 的意义全凭你的理解和库的用户对你项目领域的认识。利用我们这里讲到的技术来创建同级模块和嵌套的模块,总有一个会是你会喜欢的结构。
|
||||
|
||||
### 将模块移动到其他文件
|
||||
|
||||
@ -128,7 +129,7 @@ mod network {
|
||||
}
|
||||
```
|
||||
|
||||
<span class="caption">示例 7-3:三个模块,`client`、`network` 和 `network::server`,他们都定义于 *src/lib.rs*</span>
|
||||
<span class="caption">示例 7-3:三个模块,`client`、`network` 和 `network::server`,它们都定义于 *src/lib.rs*</span>
|
||||
|
||||
*src/lib.rs* 文件有如下层次结构:
|
||||
|
||||
@ -139,7 +140,7 @@ communicator
|
||||
└── server
|
||||
```
|
||||
|
||||
如果这些模块有很多函数,而这些函数又很长,将难以在文件中寻找我们需要的代码。因为这些函数被嵌套进一个或多个模块中,同时函数中的代码也会开始变长。这就有充分的理由将`client`、`network` 和 `server`每一个模块从 *src/lib.rs* 抽出并放入他们自己的文件中。
|
||||
如果这些模块有很多函数,而这些函数又很长,将难以在文件中寻找我们需要的代码。因为这些函数被嵌套进一个或多个模块中,同时函数中的代码也会开始变长。这就有充分的理由将`client`、`network` 和 `server`每一个模块从 *src/lib.rs* 抽出并放入它们自己的文件中。
|
||||
|
||||
首先,将 `client` 模块的代码替换为只有 `client` 模块声明,这样 *src/lib.rs* 看起来应该像这样:
|
||||
|
||||
@ -205,7 +206,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
||||
| ^
|
||||
```
|
||||
|
||||
这些警告提醒我们有从未被使用的函数。目前不用担心这些警告;在本章后面的 “使用 `pub` 控制可见性” 部分会解决他们。好消息是,他们仅仅是警告;我们的项目能够被成功编译。
|
||||
这些警告提醒我们有从未被使用的函数。目前不用担心这些警告,在本章后面的 “使用 `pub` 控制可见性” 部分会解决它们。好消息是,它们仅仅是警告,我们的项目能够被成功编译。
|
||||
|
||||
下面使用相同的模式将 `network` 模块提取到自己的文件中。删除 *src/lib.rs* 中 `network` 模块的内容并在声明后加上一个分号,像这样:
|
||||
|
||||
|
@ -26,9 +26,9 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
||||
| ^
|
||||
```
|
||||
|
||||
那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建他们的意义就在于被另一个项目而不是被我们自己使用。
|
||||
那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被 **用户** 使用,而不一定要被项目自身使用,所以不应该担心这些 `connect` 函数是未使用的。创建它们的意义就在于被另一个项目而不是被我们自己使用。
|
||||
|
||||
为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个 `connect` 库,从外部调用他们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate:
|
||||
为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个 `connect` 库,从外部调用它们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate:
|
||||
|
||||
<span class="filename">文件名: src/main.rs</span>
|
||||
|
||||
@ -109,7 +109,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
||||
|
||||
编译通过了,关于 `client::connect` 未被使用的警告消失了!
|
||||
|
||||
未被使用的代码并不总是意味着他们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除他们。这也可能是警告你出 bug 了,如果你刚刚不小心删除了库中所有这个函数的调用。
|
||||
未被使用的代码并不总是意味着它们需要被设为公有的:如果你 **不** 希望这些函数成为公有 API 的一部分,未被使用的代码警告可能是在警告你这些代码不再需要并可以安全的删除它们。这也可能是警告你出 bug 了,如果你刚刚不小心删除了库中所有这个函数的调用。
|
||||
|
||||
当然我们的情况是,**确实** 希望另外两个函数也作为 crate 公有 API 的一部分,所以让我们也将其标记为 `pub` 并去掉剩余的警告。修改 *src/network/mod.rs* 为:
|
||||
|
||||
@ -138,7 +138,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
||||
| ^
|
||||
```
|
||||
|
||||
恩,虽然将 `network::connect` 设为 `pub` 了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs* 让 `network` 也是公有的:
|
||||
虽然将 `network::connect` 设为 `pub` 了我们仍然得到了一个未被使用函数的警告。这是因为模块中的函数是公有的,不过函数所在的 `network` 模块却不是公有的。这回我们是自内向外修改库文件的,而 `client::connect` 的时候是自外向内修改的。我们需要修改 *src/lib.rs* 让 `network` 也是公有的:
|
||||
|
||||
<span class="filename">文件名: src/lib.rs</span>
|
||||
|
||||
@ -210,12 +210,12 @@ fn try_me() {
|
||||
|
||||
#### 修改错误
|
||||
|
||||
这里有一些尝试修复错误的代码修改意见。在你尝试他们之前,猜测一下他们哪个能修复错误,接着编译查看你是否猜对了,并结合私有性规则理解为什么。
|
||||
这里有一些尝试修复错误的代码修改意见。在你尝试它们之前,猜测一下它们哪个能修复错误,接着编译查看你是否猜对了,并结合私有性规则理解为什么。
|
||||
|
||||
* 如果 `inside` 模块是公有的?
|
||||
* 如果 `outermost` 是公有的而 `inside` 是私有的?
|
||||
* 如果在 `inner_function` 函数体中调用 `::outermost::middle_secret_function()`?(开头的两个冒号意味着从根模块开始引用模块。)
|
||||
|
||||
请随意设计更多的实验并尝试理解他们!
|
||||
请随意设计更多的实验并尝试理解它们!
|
||||
|
||||
接下来,让我们讨论一下使用 `use` 关键字将模块项目引入作用域。
|
@ -72,7 +72,7 @@ fn main() {
|
||||
|
||||
这使得我们可以忽略所有的模块并直接引用函数。
|
||||
|
||||
因为枚举也像模块一样组成了某种命名空间,也可以使用 `use` 来导入枚举的成员。对于任何类型的 `use` 语句,如果从一个命名空间导入多个项,可以使用大括号和逗号来列举他们,像这样:
|
||||
因为枚举也像模块一样组成了某种命名空间,也可以使用 `use` 来导入枚举的成员。对于任何类型的 `use` 语句,如果从一个命名空间导入多个项,可以使用大括号和逗号来列举它们,像这样:
|
||||
|
||||
```rust
|
||||
enum TrafficLight {
|
||||
@ -110,7 +110,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
`*` 被称为 **全局导入**(*glob*),它会导入命名空间中所有可见的项。全局导入应该保守的使用:他们是方便的,但是也可能会引入多于你预期的内容从而导致命名冲突。
|
||||
`*` 被称为 **全局导入**(*glob*),它会导入命名空间中所有可见的项。全局导入应该保守的使用:它们是方便的,但是也可能会引入多于你预期的内容从而导致命名冲突。
|
||||
|
||||
### 使用 `super` 访问父模块
|
||||
|
||||
@ -127,6 +127,7 @@ pub mod network;
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -167,7 +168,7 @@ error[E0433]: failed to resolve. Use of undeclared type or module `client`
|
||||
| ^^^^^^^^^^^^^^^ Use of undeclared type or module `client`
|
||||
```
|
||||
|
||||
编译失败了,不过为什么呢?并不需要像 *src/main.rs* 那样将 `communicator::` 置于函数前,因为这里肯定是在 `communicator` 库 crate 之内的。之所以失败的原因是路径是相对于当前模块的,在这里就是 `tests`。唯一的例外就是 `use` 语句,它默认是相对于 crate 根模块的。我们的 `tests` 模块需要 `client` 模块位于其作用域中!
|
||||
编译失败了,不过为什么呢?并不需要像 *src/main.rs* 那样将 `communicator::` 置于函数前,因为这里肯定是在 `communicator` 库 crate 之内的。失败的原因是路径是相对于当前模块的,在这里就是 `tests`。唯一的例外就是 `use` 语句,它默认是相对于 crate 根模块的。我们的 `tests` 模块需要 `client` 模块位于其作用域中!
|
||||
|
||||
那么如何在模块层次结构中回退一级模块,以便在 `tests` 模块中能够调用 `client::connect`函数呢?在 `tests` 模块中,要么可以在开头使用双冒号来让 Rust 知道我们想要从根模块开始并列出整个路径:
|
||||
|
||||
@ -216,6 +217,6 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
|
||||
|
||||
## 总结
|
||||
|
||||
现在你掌握了组织代码的核心科技!利用他们将相关的代码组合在一起、防止代码文件过长并将一个整洁的公有 API 展现给库的用户。
|
||||
现在你掌握了组织代码的核心科技!利用它们将相关的代码组合在一起、防止代码文件过长并将一个整洁的公有 API 展现给库的用户。
|
||||
|
||||
接下来,让我们看看一些标准库提供的集合数据类型,你可以利用他们编写出漂亮整洁的代码。
|
||||
接下来,让我们看看一些标准库提供的集合数据类型,你可以利用它们编写出漂亮整洁的代码。
|
||||
|
@ -14,4 +14,4 @@ Rust 标准库中包含一系列被称为 **集合**(*collections*)的非常
|
||||
|
||||
[collections]: https://doc.rust-lang.org/std/collections
|
||||
|
||||
我们将讨论如何创建和更新 vector、字符串和哈希 map,以及他们有什么不同。
|
||||
我们将讨论如何创建和更新 vector、字符串和哈希 map,以及它们有什么不同。
|
||||
|
@ -4,17 +4,17 @@
|
||||
> <br>
|
||||
> commit 6c24544ba718bce0755bdaf03423af86280051d5
|
||||
|
||||
我们要讲到的第一个类型是`Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个值,它在内存中彼此相邻的排列所有的值。vector 只能储存相同类型的值。他们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
|
||||
我们要讲到的第一个类型是`Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
|
||||
|
||||
### 新建 vector
|
||||
|
||||
为了创建一个新的,空的 vector,可以调用 `Vec::new` 函数:
|
||||
为了创建一个新的空 vector,可以调用 `Vec::new` 函数:
|
||||
|
||||
```rust
|
||||
let v: Vec<i32> = Vec::new();
|
||||
```
|
||||
|
||||
注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是同质的(homogeneous):他们可以储存很多值,不过这些值必须都是相同类型的。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用他们。现在,所有你需要知道的就是 `Vec` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。这里我们告诉 Rust `v` 这个 `Vec` 将存放 `i32` 类型的元素。
|
||||
注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是同质的(homogeneous):它们可以储存很多值,不过这些值必须都是相同类型的。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。这里我们告诉 Rust `v` 这个 `Vec` 将存放 `i32` 类型的元素。
|
||||
|
||||
在实际的代码中,一旦插入值 Rust 就可以推断出想要存放的类型,所以你很少会需要这些类型注解。更常见的做法是使用初始值来创建一个 `Vec`,而且为了方便 Rust 提供了 `vec!` 宏。这个宏会根据我们提供的值来创建一个新的 `Vec`。如下代码会新建一个拥有值 `1`、`2` 和 `3` 的 `Vec<i32>`:
|
||||
|
||||
@ -56,7 +56,7 @@ v.push(8);
|
||||
|
||||
### 读取 vector 的元素
|
||||
|
||||
现在你知道如何创建、更新和销毁 vector 了,接下来的一步最好了解一下如何读取他们的内容。有两种方法引用 vector 中储存的值。为了更加清楚的说明这个例子,我们标注这些函数返回的值的类型。
|
||||
现在你知道如何创建、更新和销毁 vector 了,接下来的一步最好了解一下如何读取它们的内容。有两种方法引用 vector 中储存的值。为了更加清楚的说明这个例子,我们标注这些函数返回的值的类型。
|
||||
|
||||
这个例子展示了访问 vector 中一个值的两种方式,索引语法或者 `get` 方法:
|
||||
|
||||
@ -80,7 +80,7 @@ let does_not_exist = v.get(100);
|
||||
|
||||
当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 `panic!`。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。
|
||||
|
||||
当 `get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)` 或 `None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果他们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户 `Vec` 当前元素的数量并再请求他们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
|
||||
当 `get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)` 或 `None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户 `Vec` 当前元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
|
||||
|
||||
#### 无效引用
|
||||
|
||||
@ -137,6 +137,6 @@ let row = vec![
|
||||
|
||||
Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加 `match` 意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。
|
||||
|
||||
如果在编写程序时不能确切无遗的知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。
|
||||
如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。
|
||||
|
||||
现在我们了解了一些使用 vector 的最常见的方式,请一定去看看标准库中 `Vec` 定义的很多其他实用方法的 API 文档。例如,除了 `push` 之外还有一个 `pop` 方法,它会移除并返回 vector 的最后一个元素。让我们继续下一个集合类型:`String`!
|
@ -10,11 +10,11 @@
|
||||
|
||||
### 什么是字符串?
|
||||
|
||||
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中事实上就只有一种字符串类型:`str`,字符串 slice,它通常以被借用的形式出现,`&str`。第四章讲到了 **字符串 slice**:他们是一些储存在别处的 UTF-8 编码字符串数据的引用。比如字符串字面值被储存在程序的二进制输出中,字符串 slice 也是如此。
|
||||
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中事实上就只有一种字符串类型:`str`,字符串 slice,它通常以被借用的形式出现,`&str`。第四章讲到了 **字符串 slice**:它们是一些储存在别处的 UTF-8 编码字符串数据的引用。比如字符串字面值被储存在程序的二进制输出中,字符串 slice 也是如此。
|
||||
|
||||
称作 `String` 的类型是由标准库提供的,而没有写进核心语言部分,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,他们通常指的是 `String` 和字符串 slice `&str`类型,而不是其中一个。这一部分大部分是关于 `String` 的,不过这些类型在 Rust 标准库中都被广泛使用。`String` 和字符串 slice 都是 UTF-8 编码的。
|
||||
称作 `String` 的类型是由标准库提供的,而没有写进核心语言部分,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 和字符串 slice `&str`类型,而不是其中一个。这一部分大部分是关于 `String` 的,不过这些类型在 Rust 标准库中都被广泛使用。`String` 和字符串 slice 都是 UTF-8 编码的。
|
||||
|
||||
Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。与 `*String`/`*Str` 的命名类似,他们通常也提供有所有权和可借用的变体,就比如说 `String`/`&str`。这些字符串类型在储存的编码或内存表现形式上可能有所不同。本章将不会讨论其他这些字符串类型;查看 API 文档来更多的了解如何使用他们以及各自适合的场景。
|
||||
Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。与 `*String`/`*Str` 的命名类似,它们通常也提供有所有权和可借用的变体,就比如说 `String`/`&str`。这些字符串类型在储存的编码或内存表现形式上可能有所不同。本章将不会讨论其他这些字符串类型;查看 API 文档来更多的了解如何使用它们以及各自适合的场景。
|
||||
|
||||
### 新建字符串
|
||||
|
||||
@ -26,7 +26,7 @@ let mut s = String::new();
|
||||
|
||||
这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。
|
||||
|
||||
通常字符串会有初始数据因为我们希望一开始就有这个字符串。为此,使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,对于字符串字面值是这样:
|
||||
通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,对于字符串字面值是这样:
|
||||
|
||||
```rust
|
||||
let data = "initial contents";
|
||||
@ -39,13 +39,13 @@ let s = "initial contents".to_string();
|
||||
|
||||
这会创建一个包含 `initial contents` 的字符串。
|
||||
|
||||
也可以使用 `String::from` 函数来从字符串字面值创建 `String`。如下等同于使用 `to_string`:
|
||||
也可以使用 `String::from` 函数来从字符串字面值创建 `String`。下面代码等同于使用 `to_string`:
|
||||
|
||||
```rust
|
||||
let s = String::from("initial contents");
|
||||
```
|
||||
|
||||
因为字符串使用广泛,这里有很多不同的用于字符串的通用 API 可供选择。他们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
|
||||
因为字符串使用广泛,这里有很多不同的用于字符串的通用 API 可供选择。它们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
|
||||
|
||||
记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据:
|
||||
|
||||
@ -195,13 +195,13 @@ let answer = &hello[0];
|
||||
224, 165, 135]
|
||||
```
|
||||
|
||||
这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解他们,也就像 Rust 的 `char` 类型那样,这些字节看起来像这样:
|
||||
这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解它们,也就像 Rust 的 `char` 类型那样,这些字节看起来像这样:
|
||||
|
||||
```text
|
||||
['न', 'म', 'स', '्', 'त', 'े']
|
||||
```
|
||||
|
||||
这里有六个 `char`,不过第四个和第六个都不是字母,他们是发音符号本身并没有任何意义。最后,如果以字形簇的角度理解,就会得到人们所说的构成这个单词的四个字母:
|
||||
这里有六个 `char`,不过第四个和第六个都不是字母,它们是发音符号本身并没有任何意义。最后,如果以字形簇的角度理解,就会得到人们所说的构成这个单词的四个字母:
|
||||
|
||||
```text
|
||||
["न", "म", "स्", "ते"]
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
最后介绍的常用集合类型是 **哈希 map**(*hash map*)。`HashMap<K, V>` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个 **哈希函数**(*hashing function*)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组,仅举几例。
|
||||
|
||||
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。
|
||||
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到它们的得分。
|
||||
|
||||
本章我们会介绍哈希 map 的基本 API,不过还有更多吸引人的功能隐藏于标准库中 `HashMap` 定义的函数中。请一如既往地查看标准库文档来了解更多信息。
|
||||
|
||||
@ -24,7 +24,7 @@ scores.insert(String::from("Yellow"), 50);
|
||||
```
|
||||
|
||||
注意必须首先 `use` 标准库中集合部分的 `HashMap`。在这三个常用集合中,`HashMap` 是最不常用的,所以并没有被 prelude 自动引用。标准库中对 `HashMap` 的支持也相对较少,例如,并没有内建的构建宏。
|
||||
像 vector 一样,哈希 map 将他们的数据储存在堆上,这个 `HashMap` 的键类型是 `String` 而值类型是 `i32`。同样类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
|
||||
像 vector 一样,哈希 map 将它们的数据储存在堆上,这个 `HashMap` 的键类型是 `String` 而值类型是 `i32`。同样类似于 vector,哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
|
||||
|
||||
另一个构建哈希 map 的方法是使用一个元组的 vector 的 `collect` 方法,其中每个元组包含一个键值对。`collect` 方法可以将数据收集进一系列的集合类型,包括 `HashMap`。例如,如果队伍的名字和初始分数分别在两个 vector 中,可以使用 `zip` 方法来创建一个元组的 vector,其中 “Blue” 与 10 是一对,依此类推。接着就可以使用 `collect` 方法将这个元组 vector 转换成一个 `HashMap`:
|
||||
|
||||
@ -163,7 +163,7 @@ println!("{:?}", map);
|
||||
|
||||
### 哈希函数
|
||||
|
||||
`HashMap` 默认使用一种密码学安全的哈希函数,它可以抵抗拒绝服务(Denial of Service, DoS)攻击。然而并不是最快的,不过为了更高的安全性值得付出一些性能的代价。如果性能监测显示此哈希函数非常慢,以致于你无法接受,你可以指定一个不同的 *hasher* 来切换为其它函数。hasher 是一个实现了 `BuildHasher` trait 的类型。第十章会讨论 trait 和如何实现他们。你并不需要从头开始实现你自己的 hasher;crates.io 有其他人分享的实现了许多常用哈希算法的 hasher 的库。
|
||||
`HashMap` 默认使用一种密码学安全的哈希函数,它可以抵抗拒绝服务(Denial of Service, DoS)攻击。然而并不是最快的,不过为了更高的安全性值得付出一些性能的代价。如果性能监测显示此哈希函数非常慢,以致于你无法接受,你可以指定一个不同的 *hasher* 来切换为其它函数。hasher 是一个实现了 `BuildHasher` trait 的类型。第十章会讨论 trait 和如何实现它们。你并不需要从头开始实现你自己的 hasher;crates.io 有其他人分享的实现了许多常用哈希算法的 hasher 的库。
|
||||
|
||||
## 总结
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user