Why can't Default be used without copy?

Here' s my code

Summary
struct Arr<T: Default, const N: usize> {
    arr: [T; N],
}

impl<T: Default + Copy, const N: usize> FromIterator<T> for Arr<T, N> {
    fn from_iter<A: IntoIterator<Item=T>>(iter: A) -> Self {
        let mut arr = [T::default(); N];
        arr.iter_mut().zip(iter.into_iter())
            .for_each(|(a, b)| *a = b);
        Self { arr }
    }
}

Can I use T without Copy trait?
Initially I used this const hack like this: const _ARR: [T; N] = [T::default(); N];
But, it seems to not work since type T cannot be imported from outer function.
Here, N is known at compile time. So, the compiler knows how many times T::default() should be called. So, I have a feeling it can be done but I wasn't able to find a way to do it.

If you want to avoid unsafe code you can use array-init:

let iter = iter.into_iter().chain(std::iter::repeat_with(T::default));
Self {
    arr: array_init::from_iter(iter).unwrap(),
}

Here is the version with unsafe:

struct Initializer<T, const N: usize> {
    array: [MaybeUninit<T>; N],
    initialized_to: usize,
}
// This will be called when `.next()` or `T::default()` panics.
impl<T, const N: usize> Drop for Initializer<T, N> {
    fn drop(&mut self) {
        unsafe { ptr::drop_in_place(&mut self.array[..self.initialized_to]) }
    }
}

/// Hack to create uninitialized arrays without `unsafe`.
struct UninitValue<T>(T);
impl<T> UninitValue<T> {
    const UNINIT: MaybeUninit<T> = MaybeUninit::uninit();
}

let mut initializer = Initializer {
    array: [UninitValue::UNINIT; N],
    initialized_to: 0,
};
for item in iter {
    initializer.array[initializer.initialized_to] = MaybeUninit::new(item);
    initializer.initialized_to += 1;
}
while initializer.initialized_to < N {
    initializer.array[initializer.initialized_to] = MaybeUninit::new(T::default());
    initializer.initialized_to += 1;
}

// Take the now fully initialized array.
let array = mem::replace(&mut initializer.array, [UninitValue::UNINIT; N]);
// Make sure not to run the destructors of the values, as we are returning them.
mem::forget(initializer);

// SAFETY: The entire array is initialized at this point
unsafe { mem::transmute_copy::<[MaybeUninit<T>; N], [T; N]>(&array) }
3 Likes

It's only called once, and then the result is copied N times.

// for example, how is [T::Default(); 3] desugared?

// not like this:
fn incorrect_example<T: Default>() {
    let array = [
         T::Default(),
         T::Default(),
         T::Default(),
    ];
}

// but like this:
fn correct_example<T: Default + Copy>() {
    let default = T::Default();
    let array = [
         default,
         default,
         default,
    ];
}

// this is why you need T: Copy

I don't know what the fix is, just wanted to explain why the compiler expects T: Copy.

1 Like

FWIW, in the future / on nightly it is possible to do this:

[(); N].map(|()| T::default()) // : [T; N]

Until then, on stable Rust, array-init is indeed the best solution out there :slightly_smiling_face:

7 Likes

There are two pull requests that might let you directly call default repeatedly, for array generate or from_fn. Or eventually we may get a complete Default for [T; N], but right now that's limited to lengths 0 through 32, because 0 was added specifically without T: Default.

1 Like

Thanks for all the replies :smiley:.

In my opinion this might a good solution if it is implemented in rust std lib.
I was thinking about another solution i.e., using macros. Since N is known here. Can I make a macro where I can call it like expand![T::default(); N] and it will expand to [T::default(), T::default(), ...N times]. I'm actually still learning about macros. This might be good project for me to implement this

That's actually not possible to make with a macro, since a macro will just see the literal token N as input and won't be able to get its value.

1 Like

Thanks for the heads up. Feels like rust is too restrictive.

One should add that (as far as I understand this) it was added this way by accident. Or at least without any discussion about it, not even highlighting or mentioning the fact that this was even the case. (And instantly stabilized as it's a trait impl. Here's the PR.) Unfortunately having such a Default implementation for zero length arrays is not totally unreasonable, so changing it can break legitimate code. So, yay stability guarantees and such…

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.