是否可以在 Rust 中使用在运行时确定的大小的堆栈分配数组? [英] Is it possible to have stack allocated arrays with the size determined at runtime in Rust?

查看:28
本文介绍了是否可以在 Rust 中使用在运行时确定的大小的堆栈分配数组?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在 Rust 中是否有等效的 alloca 来创建可变长度数组?

Is there an equivalent of alloca to create variable length arrays in Rust?

我正在寻找与以下 C99 代码等效的代码:

I'm looking for the equivalent of the following C99 code:

void go(int n) {
    int array[n];
    // ...
}

推荐答案

不可能直接,因为在支持它的语言中没有直接的语法.

It is not possible directly, as in there is not direct syntax in the language supporting it.

话虽如此,C99 的这个特殊功能值得商榷,它有一定的优势(缓存局部性和绕过 malloc)但它也有缺点(容易炸毁堆栈,阻止优化的次数,可以将静态偏移量变成动态偏移量,...).

That being said, this particular feature of C99 is debatable, it has certain advantages (cache locality & bypassing malloc) but it also has disadvantages (easy to blow-up the stack, stumps a number of optimizations, may turn static offsets into dynamic offsets, ...).

现在,我建议您改用 Vec.如果您有性能问题,那么您可以查看所谓的小向量优化".我经常在需要性能的 C 代码中看到以下模式:

For now, I would advise you to use Vec instead. If you have performance issues, then you may look into the so-called "Small Vector Optimization". I have regularly seen the following pattern in C code where performance is required:

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 很容易支持的东西(而且在某种程度上更好):

Now, this is something that Rust supports easily (and better, in a way):

enum InlineVector<T, const N: usize> {
    Inline(usize, [T; N]),
    Dynamic(Vec<T>),
}

您可以在下面看到一个简单的实现示例.

You can see an example simplistic implementation below.

然而,重要的是你现在有一个类型:

What matters, however, is that you now have a type which:

  • 当需要少于 N 个元素时使用堆栈
  • 否则移到堆中,以避免炸毁堆栈

当然,它也总是为栈上的 N 个元素保留足够的空间,即使你只使用 2 个;但是作为交换,不会调用 alloca,因此您可以避免对变体进行动态偏移的问题.

Of course, it also always reserves enough space for N elements on the stack even if you only use 2; however in exchange there is no call to alloca so you avoid the issue of having dynamic offsets to your variants.

与 C 不同的是,您仍然可以从生命周期跟踪中受益,这样您就不会在函数外部意外返回对堆栈分配数组的引用.

And contrary to C, you still benefit from lifetime tracking so that you cannot accidentally return a reference to your stack-allocated array outside the function.

注意:在 Rust 1.51 之前,您将无法通过 N 进行自定义.

我会炫耀最明显"的方法:

I will show off the most "obvious" methods:

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.

这篇关于是否可以在 Rust 中使用在运行时确定的大小的堆栈分配数组?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆