Rust 标准库中包含一系列被称为 集合(collections)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本,而根据当前情况选择合适的集合,这是一项应当逐渐掌握的技能。接下来我们将详细的了解三个在 Rust 程序中被广泛使用的集合:
- vector 允许我们一个挨着一个地储存一系列数量可变的值
- 字符串(string)是字符的集合。我们之前见过 String 类型,不过在本章我们将深入了解。
- 哈希 map(hash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做 map 的更通用的数据结构的特定实现。
对于标准库提供的其他类型的集合,请查看文档。
我们将讨论如何创建和更新 vector、字符串和哈希 map,以及它们有什么特别之处。
使用 vector 存储一列值
我们要讲到的第一个类型是 Vec<T>
,也被称为 vector
。vector
允许我们在一个单独的数据结构中储存多个值,所有值在内存中彼此相邻排列。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或购物车中商品的价格。可以查看Vector 的帮助文档。
新建 vector
为了创建一个新的空 vector
,可以调用 Vec::new
函数,如示例 8-1
所示:
let v: Vec<i32> = Vec::new();
示例 8-1:新建一个空的 vector 来储存 i32
类型的值
注意这里我们增加了一个类型标注。因为没有向这个 vector
中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这一点非常重要。vector 是用泛型实现的。现在,我们知道 Vec
是一个由标准库提供的类型,它可以存放任何类型,而当 Vec
存放某个特定类型时,那个类型位于尖括号中。在示例 8-1
中,我们告诉 Rust v
这个 Vec
将存放 i32
类型的元素。
在更实际的代码中,一旦插入值 Rust 就可以推断出想要存放的类型,所以你很少会需要这些类型标注。更常见的做法是使用初始值来创建一个 Vec
,而且为了方便 Rust 提供了 vec!
宏。这个宏会根据我们提供的值来创建一个新的 Vec
。示例 8-2
新建一个拥有值 1
、2
和 3
的 Vec<i32>
:
fn main() {
let v = vec![1, 2, 3];
println!("The vector v is {:?}", v);
}
示例 8-2:新建一个包含初值的 vector
因为我们提供了 i32
类型的初始值,Rust 可以推断出 v
的类型是 Vec<i32>
,因此类型标注就不是必须的。接下来让我们看看如何修改一个 vector
。
更新 vector
对于新建一个 vector
并向其增加元素,可以使用 push
方法,如示例 8-3
所示:
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
示例 8-3:使用 push 方法向 vector 增加值
如果想要能够改变 v
的值,必须使用 mut
关键字使其可变。放入其中的所有值都是 i32
类型的,而且 Rust 也根据数据做出如此判断,所以不需要 Vec<i32>
标注。
丢弃 vector 时也会丢弃其所有元素
类似于任何其他的 struct
,vector
在其离开作用域时会被释放,如示例 8-4
所标注的:
{
let v = vec![1, 2, 3, 4];
// 处理变量v
} // <!-- 这里 v 离开作用域并被丢弃 -->
示例 8-4:展示 vector 和其元素于何处被丢弃
当 vector
被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。这可能看起来非常直观,不过一旦开始使用 vector
元素的引用,情况就变得有些复杂了。下面让我们处理这种情况!
读取 vector 的元素
现在你知道如何创建、更新和销毁 vector
了,接下来的一步最好了解一下如何读取它们的内容。有两种方法引用 vector
中储存的值。为了更加清楚的说明这个例子,我们标注这些函数返回的值的类型。
示例 8-5
展示了访问 vector
中一个值的两种方式,索引语法或者 get
方法:
fn main() {
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
}
列表 8-5:使用索引语法或 get
方法来访问 vector 中的项
这里有两个需要注意的地方。首先,我们使用索引值 2
来获取第三个元素,索引是从 0
开始的。其次,这两个不同的获取第三个元素的方式分别为:使用 &
和 []
返回一个引用;或者使用 get
方法以索引作为参数来返回一个 Option<&T>
。
Rust 有两个引用元素的方法的原因是程序可以选择如何处理当索引值在 vector
中没有对应值的情况。作为一个例子,让我们看看如果有一个有五个元素的 vector
接着尝试访问索引为 100
的元素时程序会如何处理,如示例 8-6
所示:
let v = vec![1, 2, 3, 4, 5];
let does_not_exist = &v[100];
let does_not_exist = v.get(100);
示例 8-6:尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素
当运行这段代码,你会发现对于第一个 []
方法,当引用一个不存在的元素时 Rust 会造成 panic
。这个方法更适合当程序认为尝试访问超过 vector
结尾的元素是一个严重错误的情况,这时应该使程序崩溃。
当 get
方法被传递了一个数组外的索引时,它不会 panic
而是返回 None
。当偶尔出现超过 vector
范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 Some(&element)
或 None
的逻辑。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 None
值,你可以告诉用户当前 vector
元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则来确保 vector
内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-7
,当我们获取了 vector
的第一个元素的不可变引用并尝试在 vector
末尾增加一个元素的时候,这是行不通的:
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
}
示例 8-7:在拥有 vector 中项的引用的同时向其增加一个元素
编译会给出这个错误:
PS E:\github\rust-projects\collections> cargo run
Compiling collections v0.1.0 (E:\github\rust-projects\collections)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src\main.rs:18:5
|
16 | let first = &v[0];
| - immutable borrow occurs here
17 |
18 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
19 |
20 | println!("The first element is: {}", first);
| ----- immutable borrow later used here
For more information about this error, try `rustc --explain E0502`.
error: could not compile `collections` (bin "collections") due to previous error
示例 8-7
中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector
结尾的变化?不能这么做的原因是由于 vector
的工作方式:在 vector
的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
遍历 vector 中的元素
如果想要依次访问 vector
中的每一个元素,我们可以遍历其所有的元素而无需通过索引一次一个的访问。示例 8-8
展示了如何使用 for
循环来获取 i32
值的 vector
中的每一个元素的不可变引用并将其打印:
fn main() {
let v = vec![1, 2, 3, 4];
for i in &v {
println!("{}", i);
}
}