Rust容器和其它语言不同之处:
- 到处都是
move
和borrow
, Rust用move
来避免深拷贝. 也就是说对不能实现Copy的类型(管理堆或其它额外资源的类型)Rust插入到容器里是非常快的. - Rust容器里不存在无效值错误(invalidation error).
- Rust没有
null
类型
Vec<T>
包含长度(length), 容量(capacity)和一个指向堆上分配的内存的指针(buffer). 例如下面代码:
1
2
3
4
5
6// Create an empty vector
let mut numbers: Vec<i32> = vec![];
// Create a vector with given contents
let words = vec!["step", "on", "no", "pets"];
let mut buffer = vec![0u8; 1024]; // 1024 zeroed-out bytes上面的三个Vec在内存里布局如下:
大部分操作都返回的是reference, 因为如果返回类似是T意味着move所有权. 但是, 有一个例外: 就是如果所有元素都可以
Copy
的话, 那么这些函数返回的是一份栈上的拷贝, 比如整型. 而to_vec()
这个方法也只能作用在T
可以Copy
的数组上.1
2
3
4
5let v = [1, 2, 3, 4, 5, 6, 7, 8, 9];
assert_eq!(v.to_vec(),
vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
assert_eq!(v[0..6].to_vec(),
vec![1, 2, 3, 4, 5, 6]);
Vec<T>
的详细方法以及其它容器的使用说明这里就不过多介绍, 可以参考官网. 这里再说说一些容器的性能方面选择.
Performance of Collections
如果遍历
Vec
容器, 从C++尤其是C语言转过来的开发者会偏向使用下标的形式访问元素内容:1
2
3
4
5let arr = ['a', 'b', 'c', 'd', 'e', 'f'];
for i in 0..arr.len() {
println!("{}", arr[i]);
}这么干有两点问题:
- 这样在热点代码中很伤害性能, 因为Rust对Vec的下标访问都要做有效性判断.
- 如果这个手写下标
i
超出范围, 那么Rust程序就直接panic崩了.
推荐的做法是用之前将的
Iterator
. 这样Rust会调用next
方法一直跑下去, 直到返回None
. 并且Rust会将这个代码转换成和手写C/C++同样性能的代码, 没有额外成本.1
2
3
4
5let arr = ['a', 'b', 'c', 'd', 'e', 'f'];
for c in &arr {
println!("{}", c);
}和C/C++一样, 遍历上
Vec<T>
性能最快, 充分利用Cache-line.LinkedList<T>
是双端列表, 头尾插入、删除快, 但是访问慢, 如果只关心头尾访问速度应该选择VecDeque<T>
, 和Vec<T>
一样的实现的双端队列. 相比之下,LinkedList<T>
唯一的优势就是合并两个List的时候速度很快.Map方面有
HashMap<K, V>
和BTreeMap<K, V>
.- 前者采用SipHash 1-3散列算法(我当前版本Rust1.38), 主要预防*HashDoS attacks*, 但哈希表的实现采用的是Google的SwissTable, 速度刁刁的, 查找复杂度
O(1)
. - 后者元素有序, 采用B-Tree, 相比平衡二叉树有更好的局部性(
locality
), Cache友好, 速度快. 查找复杂度O(lgN)
.
- 前者采用SipHash 1-3散列算法(我当前版本Rust1.38), 主要预防*HashDoS attacks*, 但哈希表的实现采用的是Google的SwissTable, 速度刁刁的, 查找复杂度
Set方面也有对应的
HashSet<T>
和BTreeSet<T>
.用
Vec
,HashMap
和HashSet
足以cover绝大部分场景性能.
All images are copyrighted by original authors Jim Blandy & Jason Orendorff who wrote in the book Programming Rust.