50

Is there an equivalent of 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];
    // ...
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dogbert
  • 212,659
  • 41
  • 396
  • 397
  • you could always import the alloca function from c. if you add some kind of overflow checking you might even get it to be safe. – oli_obk Jan 09 '15 at 11:49
  • 5
    @ker: That's unlikely. `alloca` isn't really a function, it's more a compiler intrinsic. At least with LLVM, it's an actual IR instructions, so there's no direct way of "importing" it. You'd need to be able to write inline IR, which you currently can't. – DK. Jan 09 '15 at 12:59
  • that makes me wonder what those guys imported: https://github.com/tomaka/cpal/blob/master/alsa-sys/src/lib.rs#L2135 ... i was pretty sure it's a c standard function nowadays (http://man7.org/linux/man-pages/man3/alloca.3.html) – oli_obk Jan 09 '15 at 13:38
  • In general, due to how most calling conventions work, it would be impossible for `alloca` to be a regular function. A function can only change it's own stack frame and cannot change its parent's stack frame. The Notes section in the man page states that the "function" is compiler and machine dependent. This is why. – Tyler Sep 30 '21 at 15:24

2 Answers2

37

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 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 <= 64) {
    pointer = array;
} else {
    pointer = dynamic_pointer = malloc(sizeof(SomeType) * n);
}

// ...

if (dynamic_pointer) { free(dynamic_pointer); }

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:

  • 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<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()
    }
}

Usage:

fn main() {
    let mut v = SmallVector::new(1u32, 4);
    v[2] = 3;
    println!("{}: {}", v.len(), v[2])
}

Which prints 4: 3 as expected.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • @malbarbo: Indeed, and it goes about the issue of not having type level integers smartly too! I had not thought about asking for an *array* type to the user to let her specify the size. – Matthieu M. May 06 '16 at 13:41
  • 1
    The [`smallvec`](https://docs.rs/smallvec/1.4.0/smallvec/) crate provides this. – wizzwizz4 Jun 02 '20 at 19:22
  • @wizzwizz4 Partly. The `Array` trait is only implemented for a subset of sizes, and is a tad cumbersome to use. Better than nothing, but clearly a work-around for the lack of const generics. – Matthieu M. Jun 03 '20 at 11:40
  • Shouldn't the slice implementations for the `Inline` variant use that variant's size as a bound? The sample code here makes a slice of the whole len 64 array. So the slice will have the wrong length. – dubiousjim Jan 23 '21 at 11:33
  • @dubiousjim: Indeed, fixed. Also, if you want to attempt it without having to actually build 64 Ts, you'll want to use `[MaybeUninit; 64]`, but I'll leave that out for now. Finally, there's work ongoing to be able to pass inline storage to rustc's std structures, but that's very much at prototype stage at the moment. – Matthieu M. Jan 23 '21 at 11:42
  • Everyone on formus told me dynamically-sized arrays weren't possible on the stack because the size of the stack should be known at compile-time. Always thought it was weird as to how the compilers would go about determining the stack-size particularly when it encounters if-else clauses. This post was a big eye-opener for me. – zombiesauce Jun 12 '21 at 16:08
2

No.

Doing that in Rust would entail the ability to store Dynamically Sized Types (DSTs) like [i32] on the stack, which the language doesn't support.

A deeper reason is that LLVM, to my knowledge, doesn't really support this. I'm given to believe that you can do it, but it significantly interferes with optimisations. As such, I'm not aware of any near-terms plans to allow this.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
DK.
  • 55,277
  • 5
  • 189
  • 162
  • 21
    LLVM is, first and foremost, a C/C++ optimiser, so it likely supports it relatively well. – huon Jan 09 '15 at 12:14
  • 1
    Every time I've seen it brought up, someone states that dynamically resizing the stack within a block seriously impairs LLVM's ability to do optimisations. I've never found any solid evidence to either support or refute that, so I'm more or less going with what little I *have* heard. Though I'd *love* to be wrong on this one: stack DSTs would be rather nifty. :) – DK. Jan 09 '15 at 12:49
  • I'd guess "interfere with optimisers" it's a somewhat inherent property of dynamic stack allocations. (That said, it's could easily be something that the LLVM devs haven't put much effort in to handling well.) – huon Jan 09 '15 at 14:32
  • 3
    Rust now supports DSTs as a part of the Rust 1.0 alpha! http://blog.rust-lang.org/2015/01/09/Rust-1.0-alpha.html – Michael Tang Jan 09 '15 at 21:24
  • 3
    @MichaelTang: Rust has supported DSTs for a while. Unless I'm missing something, it *doesn't* support storing DSTs in variables, or returning them from functions. – DK. Jan 10 '15 at 01:02
  • 1
    @DK: You're very right. I'm ashamed to admit I spent too much time yesterday trying to make it work before realizing just DST support != what the asker wanted. – Michael Tang Jan 11 '15 at 07:51
  • 1
    Efficiency aspect aside, LLVM definitely supports stack-allocated VLAs. You just use `%vla = alloca , ` rather than `%sla = alloca [ x ]` (and the equivalent programmatic `AllocaInst` calls). – JAB Jul 14 '15 at 21:04