Rust 数组与切片:定长数组与胖指针视图
更新时间:2026-09-20。切片(slice)是对一段连续元素的借用视图,它让同一份函数能处理数组、
Vec等各种连续存储,是 Rust 里最常用的参数类型之一。
一、定长数组
rust
let a = [1, 2, 3, 4]; // [i32; 4]
let zeros = [0u8; 8]; // 8 个 0
let first = a[0]; // 编译期/运行期边界检查数组长度 N 是类型的一部分:[u8; 3] 和 [u8; 4] 不是同一类型,不能互相传参。长度运行时才确定的数据应放 Vec<T>,但函数可以只收切片而不关心底层是哪种。
二、切片是胖指针
rust
fn sum(xs: &[i32]) -> i32 { xs.iter().sum() }
let arr = [1, 2, 3, 4];
let v = vec![1, 2, 3, 4];
sum(&arr); // &[i32;4] 自动解为 &[i32]
sum(&v); // Vec 借出 &[i32]
sum(&v[1..3]); // 只借一段:[2, 3]&[T] 在内存里是两个机器字:指向首元素的指针 + 元素个数(胖指针/fat pointer),它不持有数据,只是一段连续 T 的视图。
| 类型 | 拥有数据 | 长度 | 存储 |
|---|---|---|---|
[T; N] | 是 | 编译期固定 | 通常栈上 |
Vec<T> | 是 | 运行时可变 | 堆上 |
&[T] / &mut [T] | 否(借用) | 视图长度 | 不分配 |
三、切片的范围与越界
rust
let v = vec![10, 20, 30, 40];
let s = &v[1..3]; // [20, 30],左闭右开
let h = &v[..2]; // 从头:[10, 20]
let t = &v[2..]; // 到尾:[30, 40]
let all = &v[..]; // 全量
// let bad = &v[2..=8]; // 终点越界 → panic:slice index out of range- 索引/切片越界在运行时 panic(安全终止),而不是像 C/C++ 那样产生未定义行为,这是有边界检查的代价。
- 编译器能证明下标合法时会优化掉边界检查;热路径可用迭代器/
get()避免 panic 风险。
rust
match v.get(10) { // 越界返回 None 而非 panic
Some(x) => println!("{x}"),
None => println!("out of range"),
}四、常用切分方法
rust
let v = vec![1, 2, 3, 4, 5];
let (head, tail) = v.split_first().unwrap(); // head=&1, tail=&[2..5]
for c in v.chunks(2) { /* [1,2] [3,4] [5] */ }
for w in v.windows(3) { /* [1,2,3] [2,3,4] ... */ }
for (i, x) in v.split(|x| *x == 3) { /* 按谓词切 */ }五、常见坑
- 返回局部数组/ Vec 的切片:被借数据在函数结束时释放,返回
&[T]会悬垂,应返回Vec<T>(生命周期)。 - 同时持有
&mut [T]和其它引用:违反多读一写,编译失败;可变切片在同一时刻是排他的。 - 越界直接索引导致 panic:处理外部不可信下标用
get/get_mut返回Option。 - 按字节切 String:
&s[a..b]的边界必须落在 UTF-8 字符边界,否则 panic,见 String 与 &str。 - 以为切片会拷贝:
&[T]只是指针+长度,传切片零拷贝,但也因此受借用规则约束。
总结
[T;N] 是长度写进类型、通常在栈上的定长数组,Vec 是堆上可变长容器,而 &[T]/&mut [T] 是由首元素指针和长度组成的胖指针借用视图,不持有数据也不分配,能统一借用数组、Vec 等连续存储;切片范围遵循左闭右开、越界在运行时 panic(安全终止而非 UB),不可信下标用 get 返回 Option;切片同样受多读一写和生命周期约束,不能返回局部数据的悬垂切片,切 String 时边界还必须对齐 UTF-8 字符。