如何 const 泛型数组?
解读
在国内 Rust 面试中,“const 泛型数组” 通常指“如何在泛型参数里把数组长度作为编译期常量传进去”,从而让函数、结构体或 trait 在不同长度数组上复用同一份泛型代码,同时保持零成本抽象与编译期检查。
面试官想确认三点:
- 你是否真的理解 const 泛型(
const N: usize)的语法与稳定状态; - 能否写出无需 trait 包装、无需宏、无需 Vec 分配的纯栈上实现;
- 是否知道当前 stable 通道的长度上限 32 的“老限制”已被 const 泛型打破,以及由此带来的数组 Copy/Clone/Debug 等自动实现的边界条件。
知识点
- const 泛型参数:
struct Foo<const N: usize>或fn bar<const N: usize>(a: [T; N]),稳定自 1.51。 - 数组类型
[T; N]的“长度”必须是编译期常量,const 泛型正好填补这一空缺。 - 泛型常量表达式(generic_const_exprs)仍属 nightly,面试时只需回答 stable 能力即可,不要提
#![feature(generic_const_exprs)],否则会被追问 unstable 风险。 - trait 边界中若出现
N + 1、2 * N等表达式,stable 会报错,需用类型级技巧或宏绕过;面试现场只需指出“目前 stable 不支持任意表达式”即可。 - 数组作为函数参数时,默认按值传递会移动整个栈拷贝,大数组需用
&[T; N]或&[T]避免复制;但题目问“泛型数组”而非“切片”,优先展示按值版本以体现对 const 泛型的信心。
答案
// 1. 结构体持有任意长度数组
#[derive(Debug, Clone, Copy)]
struct Vector<T, const N: usize> {
data: [T; N],
}
impl<T: Default + Copy, const N: usize> Vector<T, N> {
// 2. 关联函数:生成零向量
fn zero() -> Self {
Self { data: [T::default(); N] }
}
// 3. 方法:逐元素相加
fn add(&self, other: &Self) -> Self {
let mut result = self.data;
for i in 0..N {
result[i] = self.data[i] + other.data[i];
}
Self { data: result }
}
}
// 4. 独立函数:接收任意长度数组并返回其长度
fn array_len<T, const N: usize>(_arr: &[T; N]) -> usize {
N
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_const_generic_array() {
let a: Vector<i32, 5> = Vector::zero();
let b = Vector { data: [1, 2, 3, 4, 5] };
let c = a.add(&b);
assert_eq!(c.data, [1, 2, 3, 4, 5]);
assert_eq!(array_len(&c.data), 5);
}
}
要点强调:
<const N: usize>写在泛型列表里,与类型参数位置无关,但必须显式声明;- 数组长度 N 直接出现在类型
[T; N]中,编译器会为每个用到的 N 单态化一份代码,无运行时开销; - stable Rust 已支持任意大小的 const 泛型数组,不再受 32 限制,可直接
let x: [u8; 1024] = [0; 1024];。
拓展思考
-
如果面试官追问“如何返回不同长度的数组?”
答:函数签名必须在编译期确定长度,因此无法根据运行时值返回不同长度;可用Vec<T>或Box<[T]>做堆分配,也可用 const 泛型工厂模式让调用者指定长度。 -
“数组长度做 trait 边界”
例如只想实现Vector<f32, 3>的叉乘,可用零成本特化:impl Vector<f32, 3> { fn cross(&self, other: &Self) -> Self { ... } }无需宏,编译期只实例化一份,体现 Rust 的“静态分派 + 精确单态化”。
-
“const 泛型与 trait 对象不能共存”
dyn Vector<T, N>是非法的,因为 N 是编译期常量,而 trait 对象要求类型擦除;面试时可补充:“如需动态分发,可擦除长度到切片 &[T] 或自定义胖指针”。 -
“未来 nightly 的 generic_const_exprs 会带来什么”
可简单提及:“允许fn foo<const N: usize>() -> [u8; N + 1],但当前 stable 仍需宏或 GAT 曲线救国”,表现出对语言演进的持续跟踪意识,国内大厂看重这一点。