0

I have an odd case where I want to initialize some segments of an array as copies of an existing array, and call a function to initialize the other elements. Naively, I'd like to do something like this:

fn build_array(input: [char; 8]) -> [char; 25] {
    let mut out: [char; 25];
    out[6..10].copy_from_slice(input[0..4]);
    out[16..20].copy_from_slice(input[4..8]);

    for i in 0..25 {
        if (6..10).contains(i) || (16..20).contains(i) {
            continue;
        }
        out[i] = some_func();
    }
} 

Obviously I could just initialize the array, but that would be inefficient. I was surprised to find that wrapping the copy_from_slice() calls in an unsafe block does not make this compile. Creating multiple array segments and concatenating them doesn't seem to simplify things based on this question.

Does anyone know of an idiomatic and efficient way to accomplish what I want to do here?

Edit: some_func() here is meant as a placeholder, the elements that aren't provided in input don't all have the same value.

agd
  • 99
  • 8

1 Answers1

2

First, do not worry about the cost of initializing the elements. Especially when the optimizer may eliminate it.

If you really need that, e.g. for a very big array, the Rust way is to use MaybeUninit:

use std::mem::{self, MaybeUninit};
use std::ptr;

fn build_array(input: [char; 8]) -> [char; 25] {
    // SAFETY: `MaybeUninit` is always considered initialized (replace with
    // `MaybeUninit::uninit_array()` once stabilized).
    let mut out: [MaybeUninit<char>; 25] = unsafe { MaybeUninit::uninit().assume_init() };
    // SAFETY: source and destination derived from references, slices are of
    // the correct length (replace with `MaybeUninit::write_slice()` once stabilized).
    unsafe {
        ptr::copy_nonoverlapping(
            input[0..4].as_ptr(),
            out[6..10].as_mut_ptr().cast::<char>(),
            4,
        );
        ptr::copy_nonoverlapping(
            input[4..8].as_ptr(),
            out[16..20].as_mut_ptr().cast::<char>(),
            4,
        );
    }

    for i in 0..25 {
        if (6..10).contains(&i) || (16..20).contains(&i) {
            continue;
        }
        out[i].write(some_func());
    }

    // SAFETY: `MaybeUninit<T>` has the same layout as `T`, initialized above
    // (replace with `MaybeUninit::array_assume_init()` once stabilized).
    unsafe { mem::transmute(out) }
}

Like you can see, this involves non-trivial unsafe code, so I highly recommend to not do that unless really necessary and you know well what you're doing.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
  • Thanks for showing how this would be done! I will follow your advice and not try to optimize out the element initialization unless I really need to. – agd Mar 28 '22 at 06:59