6

Is there a way in Rust to initialize the first n elements of an array manually, and specify a default value to be used for the rest?

Specifically, when initializing structs, we can specify some fields, and use .. to initialize the remaining fields from another struct, e.g.:

let foo = Foo {
  x: 1,
  y: 2,
  ..Default::default()
};

Is there a similar mechanism for initializing part of an array manually? e.g.

let arr: [i32; 5] = [1, 2, ..3];

to get [1, 2, 3, 3, 3]?

6
  • [1, 2, ..3] is not equal to the struct update syntax since there you put whole instance after the ellipsis. What it should be is [1, 2, ..[3; 5]]. I don't think a syntax like that exist, however. Commented Dec 1, 2021 at 17:23
  • Are you looking specifically for an array, or would you be happy with a vec (it looks easy to write an efficient enough macro like vecd![1, 2, ; 3; 5]) ? Commented Dec 1, 2021 at 17:41
  • Would you like something like this ? Commented Dec 1, 2021 at 17:48
  • I was wondering if there is already a built-in solution, but these are great suggestions! Commented Dec 1, 2021 at 18:39
  • How to initialize a static array with most values the same but some values different? Commented Dec 1, 2021 at 19:26

2 Answers 2

1

Edit: I realized this can be done on stable. For the original answer, see below.

I had to juggle with the compiler so it will be able to infer the type of the array, but it works:

// A workaround on the same method on `MaybeUninit` being unstable.
// Copy-paste from https://doc.rust-lang.org/stable/src/core/mem/maybe_uninit.rs.html#943-953.
pub unsafe fn maybe_uninit_array_assume_init<T, const N: usize>(
    array: [core::mem::MaybeUninit<T>; N],
) -> [T; N] {
    // SAFETY:
    // * The caller guarantees that all elements of the array are initialized
    // * `MaybeUninit<T>` and T are guaranteed to have the same layout
    // * `MaybeUninit` does not drop, so there are no double-frees
    // And thus the conversion is safe
    (&array as *const _ as *const [T; N]).read()
}

macro_rules! array_with_default {
    (@count) => { 0usize };
    (@count $e:expr, $($rest:tt)*) => { 1usize + array_with_default!(@count $($rest)*) };

    [$($e:expr),* ; $default:expr; $default_size:expr] => {{
        // There is no hygiene for items, so we use unique names here.
        #[allow(non_upper_case_globals)]
        const __array_with_default_EXPRS_LEN: usize = array_with_default!(@count $($e,)*);
        #[allow(non_upper_case_globals)]
        const __array_with_default_DEFAULT_SIZE: usize = $default_size;
        let mut result = unsafe { ::core::mem::MaybeUninit::<
            [::core::mem::MaybeUninit<_>; {
                __array_with_default_EXPRS_LEN + __array_with_default_DEFAULT_SIZE
            }],
        >::uninit().assume_init() };

        let mut dest = result.as_mut_ptr();
        $(
            let expr = $e;
            unsafe {
                ::core::ptr::write((*dest).as_mut_ptr(), expr);
                dest = dest.add(1);
            }
        )*
        for default_value in [$default; __array_with_default_DEFAULT_SIZE] {
            unsafe {
                ::core::ptr::write((*dest).as_mut_ptr(), default_value);
                dest = dest.add(1);
            }
        }

        unsafe { maybe_uninit_array_assume_init(result) }
    }};
}

Playground.


Based on the example from @Denys, here is a macro that works on nightly. Note that I had problems matching the .. syntax (though I'm not entirely sure that's impossible; just didn't put much time into that):

#![feature(generic_const_exprs)]
#![allow(incomplete_features)]

use std::mem::MaybeUninit;

pub fn concat_arrays<T, const N: usize, const M: usize>(a: [T; N], b: [T; M]) -> [T; N + M] {
    unsafe {
        let mut result = MaybeUninit::<[T; N + M]>::uninit();
        let dest = result.as_mut_ptr().cast::<[T; N]>();
        dest.write(a);
        let dest = dest.add(1).cast::<[T; M]>();
        dest.write(b);
        result.assume_init()
    }
}

macro_rules! array_with_default {
    [$($e:expr),* ; $default:expr; $default_size:expr] => {
        concat_arrays([$($e),*], [$default; $default_size])
    };
}

fn main() {
    dbg!(array_with_default![1, 2; 3; 7]);
}

Playground.

Sign up to request clarification or add additional context in comments.

1 Comment

This is well way convoluted.
1

As another option, you can build a default filled array and just modify the positions you require in runtime:

#![feature(explicit_generic_args_with_impl_trait)]

fn array_with_default_and_positions<T: Copy, const SIZE: usize>(
    default: T,
    init_values: impl IntoIterator<Item = (usize, T)>,
) -> [T; SIZE] {
    let mut res = [default; SIZE];
    for (i, e) in init_values.into_iter() {
        res[i] = e;
    }
    res
}

Playground

Notice the use of #![feature(explicit_generic_args_with_impl_trait)],which is nightly, it could be replaced by an slice since T and usize are copy:

fn array_with_default_and_positions_v2<T: Copy, const SIZE: usize>(
    default: T,
    init_values: &[(usize, T)],
) -> [T; SIZE] {
    let mut res = [default; SIZE];
    for &(i, e) in init_values.into_iter() {
        res[i] = e;
    }
    res
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.