## vector

> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-01-vectors.md)
> <br>
> commit 6c24544ba718bce0755bdaf03423af86280051d5

我们要讲到的第一个类型是`Vec<T>`，也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个值，它在内存中彼此相邻的排列所有的值。vector 只能储存相同类型的值。他们在拥有一系列项的场景下非常实用，例如文件中的文本行或是购物车中商品的价格。

### 新建 vector

为了创建一个新的，空的 vector，可以调用`Vec::new`函数：

```rust
let v: Vec<i32> = Vec::new();
```

注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值，Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是同质的（homogeneous）：他们可以储存很多值，不过这些值必须都是相同类型的。vector 是用泛型实现的，第十章会涉及到如何对你自己的类型使用他们。现在，所有你需要知道的就是`Vec`是一个由标准库提供的类型，它可以存放任何类型，而当`Vec`存放某个特定类型时，那个类型位于尖括号中。这里我们告诉 Rust `v`这个`Vec`将存放`i32`类型的元素。

在实际的代码中，一旦插入值 Rust 就可以推断出想要存放的类型，所以你很少会需要这些类型注解。更常见的做法是使用初始值来创建一个`Vec`，而且为了方便 Rust 提供了`vec!`宏。这个宏会根据我们提供的值来创建一个新的`Vec`。如下代码会新建一个拥有值`1`、`2`和`3`的`Vec<i32>`：

```rust
let v = vec![1, 2, 3];
```

因为我们提供了`i32`类型的初始值，Rust 可以推断出`v`的类型是`Vec<i32>`，因此类型注解就不是必须的。接下来让我们看看如何修改一个 vector。

### 更新 vector

对于新建一个 vector 并向其增加元素，可以使用`push`方法：

```rust
let mut v = Vec::new();

v.push(5);
v.push(6);
v.push(7);
v.push(8);
```

如第三章中讨论的任何变量一样，如果想要能够改变它的值，必须使用`mut`关键字使其可变。放入其中的所有值都是`i32`类型的，而且 Rust 也根据数据如此判断，所以不需要`Vec<i32>`注解。

### 丢弃 vector 时也会丢弃其所有元素

类似于任何其他的`struct`，vector 在其离开作用域时会被释放：

```rust
{
    let v = vec![1, 2, 3, 4];

    // do stuff with v

} // <- v goes out of scope and is freed here
```

当 vector 被丢弃时，所有其内容也会被丢弃，这意味着这里它包含的整数将被清理。这可能看起来非常直观，不过一旦开始使用 vector 元素的引用情况就变得有些复杂了。下面让我们处理这种情况！

### 读取 vector 的元素

现在你知道如何创建、更新和销毁 vector 了，接下来的一步最好了解一下如何读取他们的内容。有两种方法引用 vector 中储存的值。为了更加清楚的说明这个例子，我们标注这些函数返回的值的类型。

这个例子展示了访问 vector 中一个值的两种方式，索引语法或者`get`方法：

```rust
let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2];
let third: Option<&i32> = v.get(2);
```

这里有一些需要注意的地方。首先，我们使用索引值`2`来获取第三个元素，索引是从 0 开始的。其次，这两个不同的获取第三个元素的方式分别为：使用`&`和`[]`返回一个引用；或者使用`get`方法以索引作为参数来返回一个`Option<&T>`。

Rust 有两个引用元素的方法的原因是程序可以选择如何处理当索引值在 vector 中没有对应值的情况。例如如下情况，如果有一个有五个元素的 vector 接着尝试访问索引为 100 的元素，程序该如何处理：

```rust,should_panic
let v = vec![1, 2, 3, 4, 5];

let does_not_exist = &v[100];
let does_not_exist = v.get(100);
```

当运行这段代码，你会发现对于第一个`[]`方法，当引用一个不存在的元素时 Rust 会造成`panic!`。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况，这时应该使程序崩溃。

当`get`方法被传递了一个数组外的索引时，它不会 panic 而是返回`None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理`Some(&element)`或`None`的逻辑，如第六章讨论的那样。例如，索引可能来源于用户输入的数字。如果他们不慎输入了一个过大的数字那么程序就会得到`None`值，你可以告诉用户`Vec`当前元素的数量并再请求他们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多！

#### 无效引用

一旦程序获取了一个有效的引用，借用检查器将会执行第四章讲到的所有权和借用规则来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于这个例子，当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候：

```rust,ignore
let mut v = vec![1, 2, 3, 4, 5];

let first = &v[0];

v.push(6);
```

编译会给出这个错误：

```
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as
immutable
  |
4 | let first = &v[0];
  |              - immutable borrow occurs here
5 |
6 | v.push(6);
  | ^ mutable borrow occurs here
7 | }
  | - immutable borrow ends here
```

这些代码看起来应该能够运行：为什么第一个元素的引用会关心 vector 结尾的变化？不能这么做的原因是由于 vector 的工作方式。在 vector 的结尾增加新元素是，在没有足够空间将所有所有元素依次相邻存放的情况下，可能会要求分配新内存并将老的元素拷贝到新的空间中。这时，第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。

> 注意：关于更多内容，查看 Nomicon *https://doc.rust-lang.org/stable/nomicon/vec.html*

### 使用枚举来储存多种类型

在本章的开始，我们提到 vector 只能储存相同类型的值。这是很不方便的；绝对会有需要储存一系列不同类型的值的用例。幸运的是，枚举的成员都被定义为相同的枚举类型，所以当需要在 vector 中储存不同类型值时，我们可以定义并使用一个枚举！

例如，假如我们想要从电子表格的一行中获取值，而这一行的有些列包含数字，有些包含浮点值，还有些是字符串。我们可以定义一个枚举，其成员会存放这些不同类型的值，同时所有这些枚举成员都会被当作相同类型，那个枚举的类型。接着可以创建一个储存枚举值的 vector，这样最终就能够储存不同类型的值了：

```rust
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row = vec![
    SpreadsheetCell::Int(3),
    SpreadsheetCell::Text(String::from("blue")),
    SpreadsheetCell::Float(10.12),
];
```

<span class="caption">Listing 8-1: Defining an enum to be able to hold
different types of data in a vector</span>

Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型，那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加`match`意味着 Rust 能在编译时就保证总是会处理所有可能的情况，正如第六章讲到的那样。

如果在编写程序时不能确切无遗的知道运行时会储存进 vector 的所有类型，枚举技术就行不通了。相反，你可以使用 trait 对象，第十七章会讲到它。

现在我们了解了一些使用 vector 的最常见的方式，请一定去看看标准库中`Vec`定义的很多其他实用方法的 API 文档。例如，除了`push`之外还有一个`pop`方法，它会移除并返回 vector 的最后一个元素。让我们继续下一个集合类型：`String`！