Home > Software design >  Initialize slice of an uninitialized array
Initialize slice of an uninitialized array

Time:03-28

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?

CodePudding user response:

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.

CodePudding user response:

You can simply initialise the array with the base some_func(), and then fill whatever needed instead of the other way around:

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

Playground

  • Related