数组、切片和 Vec 是新手最先用到、也最容易混的三样东西。这篇用真实的编译与运行结果分清:谁在栈上、谁在堆上,&[T] 到底是什么,Vec 容量怎么增长,以及越界时编译器和运行时分别怎么反应。
源码:
1 | use std::mem::size_of_val;
2 | fn main() {
3 | let a = [10, 20, 30, 40, 50];
4 | println!("a 的类型长度 = {},占 {} 字节(5 个 i32)", a.len(), size_of_val(&a));
5 | let i = std::env::args().count() + 6; // 运行时才知道:7
6 | println!("访问 a[{i}] ...");
7 | println!("{}", a[i]);
8 | }
编译:成功
运行(退出码 101):
a 的类型长度 = 5,占 20 字节(5 个 i32)
访问 a[7] ...
thread 'main' (6929057) panicked at <实验目录>/src/u6a.rs:7:20:
index out of bounds: the len is 5 but the index is 7
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
源码:
1 | fn main() {
2 | let a = [10, 20, 30];
3 | println!("{}", a[5]);
4 | }
编译:失败
error: this operation will panic at runtime
--> <实验目录>/src/u6b.rs:3:20
|
3 | println!("{}", a[5]);
| ^^^^ index out of bounds: the length is 3 but the index is 5
|
= note: `#[deny(unconditional_panic)]` on by default
error: aborting due to 1 previous error 源码:
1 | use std::mem::size_of;
2 | fn sum(xs: &[i32]) -> i32 { xs.iter().sum() }
3 | fn main() {
4 | let arr = [1, 2, 3, 4, 5];
5 | let v = vec![1, 2, 3, 4, 5];
6 | println!("size_of::<&i32>() = {} 字节(普通引用:一个地址)", size_of::<&i32>());
7 | println!("size_of::<&[i32]>() = {} 字节(切片:地址 + 长度)", size_of::<&[i32]>());
8 | println!("sum(&arr) = {}", sum(&arr));
9 | println!("sum(&v) = {}", sum(&v));
10 | println!("sum(&arr[1..4]) = {}", sum(&arr[1..4]));
11 | println!("sum(&v[..2]) = {}", sum(&v[..2]));
12 | let s = &v[1..3];
13 | println!("切片 s = {:?},s.len() = {},s 的数据地址比 v 的数据地址大 {} 字节", s, s.len(), s.as_ptr() as usize - v.as_ptr() as usize);
14 | }
编译:成功
运行(退出码 0):
size_of::<&i32>() = 8 字节(普通引用:一个地址)
size_of::<&[i32]>() = 16 字节(切片:地址 + 长度)
sum(&arr) = 15
sum(&v) = 15
sum(&arr[1..4]) = 9
sum(&v[..2]) = 3
切片 s = [2, 3],s.len() = 2,s 的数据地址比 v 的数据地址大 4 字节
源码:
1 | fn main() {
2 | let v = vec![1, 2, 3];
3 | let (a, b) = (3, 2);
4 | println!("{:?}", &v[a..b]);
5 | }
编译:成功
运行(退出码 101):
thread 'main' (6929196) panicked at <实验目录>/src/u6d.rs:4:24:
slice index starts at 3 but ends at 2
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace胖指针的更多细节见Slice 的胖指针。
源码:
1 | use std::mem::size_of;
2 | fn main() {
3 | println!("size_of::<Vec<i32>>() = {} 字节(指针、长度、容量各 8 字节)", size_of::<Vec<i32>>());
4 | let mut v: Vec<i32> = Vec::new();
5 | println!("Vec::new():len={} cap={}(还没有分配堆内存)", v.len(), v.capacity());
6 | let mut last = v.capacity();
7 | for i in 0..40 {
8 | v.push(i);
9 | if v.capacity() != last { println!("push 第 {:>2} 个元素后:cap {:>2} → {:>2}", v.len(), last, v.capacity()); last = v.capacity(); }
10 | }
11 | let w: Vec<i32> = Vec::with_capacity(100);
12 | println!("with_capacity(100):len={} cap={}", w.len(), w.capacity());
13 | let mut z = vec![1, 2, 3];
14 | z.reserve(10); println!("vec![1,2,3] 后 reserve(10):cap={}", z.capacity());
15 | z.shrink_to_fit(); println!("shrink_to_fit 后:cap={}", z.capacity());
16 | }
编译:成功
运行(退出码 0):
size_of::<Vec<i32>>() = 24 字节(指针、长度、容量各 8 字节)
Vec::new():len=0 cap=0(还没有分配堆内存)
push 第 1 个元素后:cap 0 → 4
push 第 5 个元素后:cap 4 → 8
push 第 9 个元素后:cap 8 → 16
push 第 17 个元素后:cap 16 → 32
push 第 33 个元素后:cap 32 → 64
with_capacity(100):len=0 cap=100
vec![1,2,3] 后 reserve(10):cap=13
shrink_to_fit 后:cap=3扩容的实现细节与成本见Vec 扩容机制。
编译:成功
运行(退出码 0):
局部变量地址 = 0x16fd726ff
数组元素地址 = 0x16fd72700(与局部变量相差 1 字节)
Vec 元素地址 = 0x100795300(与局部变量相差 1868420095 字节)
Vec 本身(三个字)地址 = 0x16fd72710(在栈上)地址是每次运行都会变的数字,这里只看「相差多远」。栈与堆的更多讨论见Rust 栈与堆分配。
编译:成功
运行(退出码 0):
v.get(1) = Some(20)
v.get(9) = None
v.get(1..3) = Some([20, 30])
v.get(2..9) = None
v.first() = Some(10),v.last() = Some(30)