是否可以在 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