patternrustModerate
Is it possible to have stack allocated arrays with the size determined at runtime in Rust?
Viewed 0 times
possiblewithhavearraysruntimethestackdeterminedrustsize
Problem
Is there an equivalent of
I'm looking for the equivalent of the following C99 code:
alloca to create variable length arrays in Rust?I'm looking for the equivalent of the following C99 code:
void go(int n) {
int array[n];
// ...
}
Solution
It is not possible directly, as in there is not direct syntax in the language supporting it.
That being said, this particular feature of C99 is debatable, it has certain advantages (cache locality & bypassing
For now, I would advise you to use
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, ...).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
Now, this is something that Rust supports easily (and better, in a way):
enum InlineVector {
Inline(usize, [T; N]),
Dynamic(Vec),
}
You can see an example simplistic implementation below.
What matters, however, is that you now have a type which:
- uses the stack when less than N elements are required
- moves off to the heap otherwise, to avoid blowing up the stack
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.
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.
Note: prior to Rust 1.51, you wouldn't be able to customize by N.
I will show off the most "obvious" methods:
enum SmallVector {
Inline(usize, [T; N]),
Dynamic(Vec),
}
impl SmallVector {
fn new(v: T, n: usize) -> Self {
if n SmallVector {
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 Deref for SmallVector {
type Target = [T];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl DerefMut for SmallVector {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_mut_slice()
}
}
Usage:
fn main() {
let mut v = SmallVector::new(1u32, 4);
v[2] = 3;
println!("{}: {}", v.len(), v[2])
}
Which prints 4: 3` as expected.Code Snippets
enum InlineVector<T, const N: usize> {
Inline(usize, [T; N]),
Dynamic(Vec<T>),
}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])
}Context
Stack Overflow Q#27859822, score: 42
Revisions (0)
No revisions yet.