0
use std::rc::Rc;

pub fn test() -> Rc<[usize; 1024]> {
    Rc::new([42; 1024])
}

If I compile this code, I can see that Rust fills up an array with 42 on the stack, then allocates on the heap and invokes memcpy to copy over the values from the stack to the newly allocated area which is wasteful.

What's the easiest way to ask Rust to initialize the array directly on the heap without initializing it on the stack first to avoid the memcpy?

David Frank
  • 5,918
  • 8
  • 28
  • 43

3 Answers3

0

As far as I know there is currently no way to allocate an Rc<[T; N]> directly without copying the data from the stack on stable. On nightly one can initialize the Rc in place by hand with something like this:

#![feature(new_uninit)]
use std::rc::Rc;
use std::mem::MaybeUninit;

pub fn test<const N: usize>() -> Rc<[usize; N]> {
    let mut r: Rc<[MaybeUninit<usize>]> = Rc::new_uninit_slice(N);
    for f in Rc::get_mut(&mut r).unwrap().iter_mut() {
        f.write(42);
    }
    // SAFETY:
    // * all elements are initialized
    // * the metadata can safely be dropped since it's encoded in the returned type
    // * layout of `MaybeUninit<T>` is the same as `T`
    unsafe { Rc::from_raw(Rc::into_raw(r).cast()) }
}
cafce25
  • 15,907
  • 4
  • 25
  • 31
0

No need to reach for nightly, this can be done on stable (note: this only does not copy with optimizations enabled):

use std::mem::MaybeUninit;
use std::rc::Rc;

pub fn test() -> Rc<[usize; 1024]> {
    let mut rc = Rc::new(MaybeUninit::<[usize; 1024]>::uninit());
    // SAFETY: `MaybeUninit<[T; N]>` and `[MaybeUninit<T>; N]` have the same layout,
    // and both can stay uninitialized.
    let data = unsafe {
        &mut *(Rc::get_mut(&mut rc).unwrap() as *mut MaybeUninit<[usize; 1024]>
            as *mut [MaybeUninit<usize>; 1024])
    };
    for item in data {
        item.write(42);
    }
    // SAFETY: `MaybeUninit<T>` and `T` have the same layout, and we initialized it above.
    unsafe { Rc::from_raw(Rc::into_raw(rc).cast::<[usize; 1024]>()) }
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
0

Building on this answer, you can first create a Box<[usize; 1024]>, then convert that to Rc since Rc<T> implements From<Box<T>>:

use std::rc::Rc;

pub fn test() -> Rc<[usize; 1024]> {
    let boxed_slice = vec![42; 1024].into_boxed_slice();
    let ptr = Box::into_raw(boxed_slice) as *mut [usize; 1024];
    // SAFETY: the data of a `[T; N]` has the same layout as that of
    // a `[T]` and we allocated a slice of the right size.
    let b = unsafe { Box::from_raw(ptr) };
    b.into()
}

Godbolt

Jmb
  • 18,893
  • 2
  • 28
  • 55