是否可以在 Rust 中使用在运行时确定大小的堆栈分配数组?
Is it possible to have stack allocated arrays with the size determined at runtime in Rust?
是否有 alloca
在 Rust 中创建可变长度数组的等价物?
我正在寻找与以下 C99 代码等效的代码:
void go(int n) {
int array[n];
// ...
}
没有
在 Rust 中这样做需要能够在堆栈上存储 [i32]
等动态大小类型 (DST),而语言不支持。
更深层次的原因是,据我所知,LLVM 并不真正支持这一点。我一直相信您可以 做到这一点,但它显着 会干扰优化。因此,我不知道有任何近期计划允许这样做。
不可能直接,因为在支持它的语言中没有直接语法。
也就是说,C99 的这个特性值得商榷,它有一定的优点(缓存局部性和绕过 malloc
)但它也有缺点(容易炸毁堆栈,树桩一些优化,可能会将静态偏移量转换为动态偏移量,...)。
现在,我建议您改用 Vec
。如果您有性能问题,那么您可以查看所谓的“小向量优化”。我经常在需要性能的 C 代码中看到以下模式:
SomeType array[64] = {};
SomeType* pointer, *dynamic_pointer;
if (n <= 64) {
pointer = array;
} else {
pointer = dynamic_pointer = malloc(sizeof(SomeType) * n);
}
// ...
if (dynamic_pointer) { free(dynamic_pointer); }
现在,这是 Rust 很容易支持的东西(在某种程度上更好):
enum InlineVector<T, const N: usize> {
Inline(usize, [T; N]),
Dynamic(Vec<T>),
}
您可以在下面看到一个简单的实施示例。
然而,重要的是您现在拥有一个类型:
- 当需要少于 N 个元素时使用堆栈
- 否则移到堆中,以避免炸毁堆栈
当然,它也总是为堆栈上的N个元素预留足够space,即使你只使用2个;但是作为交换,没有调用 alloca
所以你避免了对你的变体有动态偏移的问题。
与 C 不同,您仍然受益于生命周期跟踪,这样您就不会意外地 return 在函数外部引用您的堆栈分配数组。
注意:在 Rust 1.51 之前,您无法通过 N 进行自定义。
我会展示最“明显”的方法:
enum SmallVector<T, const N: usize> {
Inline(usize, [T; N]),
Dynamic(Vec<T>),
}
impl<T: Copy + Clone, const N: usize> SmallVector<T, N> {
fn new(v: T, n: usize) -> Self {
if n <= N {
Self::Inline(n, [v; N])
} else {
Self::Dynamic(vec![v; n])
}
}
}
impl<T, const N: usize> SmallVector<T, N> {
fn as_slice(&self) -> &[T] {
match self {
Self::Inline(n, array) => &array[0..*n],
Self::Dynamic(vec) => vec,
}
}
fn as_mut_slice(&mut self) -> &mut [T] {
match self {
Self::Inline(n, array) => &mut array[0..*n],
Self::Dynamic(vec) => vec,
}
}
}
use std::ops::{Deref, DerefMut};
impl<T, const N: usize> Deref for SmallVector<T, N> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for SmallVector<T, N> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
用法:
fn main() {
let mut v = SmallVector::new(1u32, 4);
v[2] = 3;
println!("{}: {}", v.len(), v[2])
}
按预期打印 4: 3
。
是否有 alloca
在 Rust 中创建可变长度数组的等价物?
我正在寻找与以下 C99 代码等效的代码:
void go(int n) {
int array[n];
// ...
}
没有
在 Rust 中这样做需要能够在堆栈上存储 [i32]
等动态大小类型 (DST),而语言不支持。
更深层次的原因是,据我所知,LLVM 并不真正支持这一点。我一直相信您可以 做到这一点,但它显着 会干扰优化。因此,我不知道有任何近期计划允许这样做。
不可能直接,因为在支持它的语言中没有直接语法。
也就是说,C99 的这个特性值得商榷,它有一定的优点(缓存局部性和绕过 malloc
)但它也有缺点(容易炸毁堆栈,树桩一些优化,可能会将静态偏移量转换为动态偏移量,...)。
现在,我建议您改用 Vec
。如果您有性能问题,那么您可以查看所谓的“小向量优化”。我经常在需要性能的 C 代码中看到以下模式:
SomeType array[64] = {};
SomeType* pointer, *dynamic_pointer;
if (n <= 64) {
pointer = array;
} else {
pointer = dynamic_pointer = malloc(sizeof(SomeType) * n);
}
// ...
if (dynamic_pointer) { free(dynamic_pointer); }
现在,这是 Rust 很容易支持的东西(在某种程度上更好):
enum InlineVector<T, const N: usize> {
Inline(usize, [T; N]),
Dynamic(Vec<T>),
}
您可以在下面看到一个简单的实施示例。
然而,重要的是您现在拥有一个类型:
- 当需要少于 N 个元素时使用堆栈
- 否则移到堆中,以避免炸毁堆栈
当然,它也总是为堆栈上的N个元素预留足够space,即使你只使用2个;但是作为交换,没有调用 alloca
所以你避免了对你的变体有动态偏移的问题。
与 C 不同,您仍然受益于生命周期跟踪,这样您就不会意外地 return 在函数外部引用您的堆栈分配数组。
注意:在 Rust 1.51 之前,您无法通过 N 进行自定义。
我会展示最“明显”的方法:
enum SmallVector<T, const N: usize> {
Inline(usize, [T; N]),
Dynamic(Vec<T>),
}
impl<T: Copy + Clone, const N: usize> SmallVector<T, N> {
fn new(v: T, n: usize) -> Self {
if n <= N {
Self::Inline(n, [v; N])
} else {
Self::Dynamic(vec![v; n])
}
}
}
impl<T, const N: usize> SmallVector<T, N> {
fn as_slice(&self) -> &[T] {
match self {
Self::Inline(n, array) => &array[0..*n],
Self::Dynamic(vec) => vec,
}
}
fn as_mut_slice(&mut self) -> &mut [T] {
match self {
Self::Inline(n, array) => &mut array[0..*n],
Self::Dynamic(vec) => vec,
}
}
}
use std::ops::{Deref, DerefMut};
impl<T, const N: usize> Deref for SmallVector<T, N> {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<T, const N: usize> DerefMut for SmallVector<T, N> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
用法:
fn main() {
let mut v = SmallVector::new(1u32, 4);
v[2] = 3;
println!("{}: {}", v.len(), v[2])
}
按预期打印 4: 3
。