Rust 中的数组[T; N]和动态数组Vec<T>都是连续存储的数据结构,但底层设计和适用场景差别很大。我整理了几个关键区别,并附上实际判断方法,方便你在写代码时快速决策。
内存分配:栈 vs 堆
Rust中的数组([T; N])在编译时大小固定,内存在栈上分配,不涉及堆分配开销。而Vec在堆上分配内存,其容量可以动态增长。当你需要存储大小在编译期已知且不会改变的数据时,优先使用数组;若数据量在运行时才能确定或需要增删,则用Vec。检查方法:使用std::mem::size_of查看数组在栈上的大小,而Vec只存储指针、长度和容量。潜在风险:数组元素过多可能导致栈溢出(通常栈大小约8MB),而Vec没有这个限制。
这里有一个简单的验证思路:如果你不确定数据量是否会超过栈的承受范围,可以先估算一下数组大小。比如一个包含 100 万个 u64 的数组,大小约为 8MB,刚好接近默认栈上限,这种情况下建议改用 Vec 或者 Box<[u64]>。反过来,如果你处理的是几十个元素的集合,数组往往更高效,因为它避免了堆分配和释放的开销。
长度可变性:编译期固定 vs 运行时灵活
数组长度在声明后不可变,任何改变长度的操作(如push、pop)都会导致编译错误。Vec则支持运行时改变长度,通过push追加元素、pop移除尾部元素。判断条件:如果代码中需要对集合进行增删操作,必须使用Vec;如果只是固定大小的遍历或访问,数组更合适。常见坑:误将数组赋值给Vec类型变量会导致类型不匹配;使用Vec时频繁push可能触发多次内存重分配,可预先with_capacity指定容量以避免性能抖动。
具体做法:当你事先知道大概要存多少个元素,用 Vec::with_capacity(n) 初始化,而不是反复 push。比如写一个解析器,预计最多 1024 个 token,那就 let mut tokens = Vec::with_capacity(1024);。这样能减少重新分配的次数。如果连上界都不清楚,就交给 Vec 自动扩容,但要注意在性能敏感的热循环里最好避免。
类型系统:长度作为类型的一部分
数组类型包含长度信息,例如[i32; 3]与[i32; 4]是不同类型,这允许编译器在编译期检查越界(避免运行时panic)。Vec的类型不包含长度,运行时越界检查通过panic或Option返回值处理。检查方法:使用[T; N]时,索引编译时已知可越界检查;运行时索引则同Vec一样需要处理。风险边界:数组索引超出长度在编译时可能警告,但运行时仍可能panic(通过调试模式检查),务必确保索引安全。
利用这一点,你可以在设计接口时用数组作为参数来强制长度约束。比如一个处理三维坐标的函数,参数类型用 [f64; 3],调用方就必须传入恰好三个元素,编译器会替你检查。不过这也意味着灵活性降低——如果将来需要支持任意长度的点集,就要把参数改成 &[f64] 切片。我个人的做法是:内部核心逻辑用数组利用编译期检查,对外暴露的 API 尽量用切片。
实践选择:从场景出发
综合来看,选数组还是 Vec 取决于你对数据生命周期的掌控程度。下面几个场景可以帮你快速判断:
- 固定大小的查找表或缓存:例如 256 字节的字节反转表,直接定义
[u8; 256],放在栈上访问最快。 - 需要频繁 push/pop 的缓冲:比如从网络读取不定长数据,用 Vec 最自然。
- 函数需要返回集合:如果返回大小固定(例如某个数学运算产生定长结果),返回数组;否则返回 Vec,或者用
impl Into<Vec<T>>给调用方选择权。 - 嵌入式或 no_std 环境:默认没有分配器,只能用数组或者固定容量数组(如
heapless::Vec),不能在堆上分配。
另外提一下性能:数组的索引访问通常比 Vec 略快(因为少一次指针间接访问),但这个差距在多数应用里可以忽略。真正需要在意的是堆分配引起的不确定性——如果你的循环里反复创建和销毁 Vec,建议考虑复用数组或使用内存池。
转换与互操作:用好切片
无论你选数组还是 Vec,对外交互时最好统一成切片 &[T] 或 &mut [T]。数组可以通过 &a[..] 或 as_slice() 轻松转换,Vec 也一样。这样函数可以同时接受两种类型,调用方无需关心内部实现。当需要从数组转成 Vec 时,Vec::from(&a[..]) 会分配新内存并拷贝,注意开销。反过来从 Vec 取固定长度数组可以用 try_into(),返回 Result<[T; N], Vec<T>>,长度不匹配时错误处理。
一个小建议:如果你写库函数,参数类型优先用 &[T],而不是 &Vec<T> 或 &[T; N]。这样最灵活,也符合 Rust 社区的习惯。
最后,不要纠结于“哪个更快”,多数场景下先让代码正确、可维护,再用基准测试工具(比如 criterion)确认热点。数组和 Vec 的区别本质上是编译期约束与运行时灵活性的取舍,选对了省心,选错了也容易重构。