Rust 标准库中定义类 集合(collections)数据结构。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的。因此,这些数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。接下来详细介绍三个在 Rust 中广泛使用的集合:
vector
:允许我们按顺序储存一系列数量可变的值字符串
(
string
)
:字符的集合。哈希 map
(
hash map
)
:允许我们将值(value)与一个特定的键(key)相关联。
vector
首先是 Vec<T>
,也被称为 vector。它允许我们储存多个相同类型的值,所有值在内存中彼此相邻排列。类似于其他语言中的数组。
新建 Vector
调用 Vec::new
函数创建一个新的空 vector:
let v: Vec<i32> = Vec::new();
💡 这里我们增加了一个类型标注。因为没有向这个 vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。
Vec
是一个由标准库提供的类型,它可以存放任何类型。Vec<i32>
是泛型,告诉编译器你创建存放什么类型的 Vec
。而在大多数情况中,只要你在创建时初始化值,编译器就可以推断出想要存放的类型。
为了方便,Rust 提供了 vec!
宏,来帮助我们方便地初始值来创建一个 Vec
:
let v = vec![1, 2, 3];
修改 Vector
可使用 push()
方法在队尾增加一个值:
// 如果想要能够改变它的值,必须使用 mut 关键字使其可变
let mut v = vec![1, 2, 3];
v.push(4);
v.push(5);
获取 Vector
Rust 提供了两种方法:
// 索引语法:
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
// get 方法
match v.get(2) {Some(third) => println!("The third element is {}", third),None => println!("There is no third element."),
}
对于索引语法,当引用一个不存在的元素时 Rust 会造成 panic。Rust 认为尝试访问超过 vector 结尾的元素是一个严重错误,这时应该使程序崩溃。而对于 get
方法,当被传递了一个数组外的索引值时,它不会 panic 而是返回 None
。
let v = vec![1, 2, 3, 4, 5];
let does_not_exist = &v[100];
let does_not_exist = v.get(100);
💡 当我们面临索引可能来源于用户输入的数字这种不确定的情况时,我们希望在发生越界时不会直接
panic
而是正常做出对应操作,就可以使用get
方法。
Immutable
一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则来确保 vector 内容的这个引用和任何其他引用保持有效。
观察下面的代码:
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("The first element is: {}", first);
我第一次看到这段代码感觉是完全没有问题的,在 C 语言中我可以这样写:
#include <iostream>
using namespace std;
int main() {int arr[10] = {1, 2, 3};// printf("%d%d", arr[1], arr[5]);int *p = arr;p[5] = 5;printf("%d%d", p[1], p[5]);return 0;
}
但在 Rust 中,这样是无法通过编译的

你肯定想,为什么第一个元素的引用会关心 vector 结尾的变化?这是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
💡 vector 的容量
capacity
是为将添加到 vector 上的 future 元素分配的空间量。请勿将其与 vector 的长度混淆,后者指定 vector 中的实际元素数量。 如果 vector 的长度超过其容量,则其容量将自动增加,但必须重新分配其元素。例如,容量为 10 且长度为 0 的 vector 将是一个空的 vector,具有 10 个以上元素的空间。将 10 个或更少的元素压入 vector 不会改变其容量或引起重新分配。 但是,如果 vector 的长度增加到 11,则必须重新分配,这可能会很慢。因此如果你有需要,建议尽可能使用
Vec::with_capacity
来指定 vector 希望达到的大小。
遍历 Vector
我们可以遍历可变 vector 的每一个元素的可变引用来改变他们。
let mut v = vec![100, 32, 57];
for i in &mut v {*i += 50;
}
💡 为了修改可变引用所指向的值,必须使用解引用运算符(
*
)获取i
中的值。
存储不同类型的值
Rust 规定 vector 只能储存相同类型的值。这很不方便。很多情况下你需要储存一系列不同类型的值的用例。而在 Rust 中,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用枚举:
enum SpreadsheetCell {Int(i32),Float(f64),Text(String),
}
let row = vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from("blue")),SpreadsheetCell::Float(10.12),
];
Rust 在编译时就必须准确的知道 vector 中类型的原因:
- 需要知道储存每个元素到底需要多少内存。
- 可以准确的知道这个 vector 中允许什么类型。
💡 如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。在 JavaScript 中,我们经常遇到的一个bug就是一个对象没有这个匹配的方法,静态类型能够避免这种问题,而使用枚举外加
match
意味着 Rust 能在编译时就保证总是会处理所有可能的情况。
如果在编写程序时不能肯定运行时会储存进 vector 的所有类型,枚举技术就行不通了。这时你需要使用 trait 对象,我们将在后面介绍到。
销毁 Vector
按照之前提到的作用域 scope
概念,vector 在其离开作用域时会被释放。
{let v = vec![1, 2, 3, 4];// ...
} // <- 这里 v 离开作用域并被丢弃
当 vector 被丢弃时,所有其内容也会被丢弃,这意味着它所包含的整数也将被全部清理。
总结
今天我们介绍了 Rust 中的 Vector
,在其他语言中我们通常把他叫做数组。Rust 的数组 Vec
是一个 (指针,容量,长度) 三元组,指向堆上一处连续的内存:
ptrlencapacity +--------+--------+--------+ | 0x0123 |2 |4 | +--------+--------+--------+|v
Heap +--------+--------+--------+--------+ |'a' |'b' | uninit | uninit | +--------+--------+--------+--------+
接下来我们会介绍完剩下两个类型:String
和HashMap
。值得一提的是,Rust 中的 String 看起来可能并不是那么顺眼,这是因为字符串本身就是复杂的:如utf8编码是不定长的、遍历的效率太低无法满足下标访问的要求等等。
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享