How to default-initialize a struct containing an array in Rust?
Asked Answered
R

5

20

What is the recommended way to declare a struct that contains an array, and then create a zero-initialized instance?

Here is the struct:

#[derive(Default)]
struct Histogram {
    sum: u32,
    bins: [u32; 256],
}

and the compiler error:

error[E0277]: the trait bound `[u32; 256]: std::default::Default` is not satisfied
 --> src/lib.rs:4:5
  |
4 |     bins: [u32; 256],
  |     ^^^^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `[u32; 256]`
  |
  = help: the following implementations were found:
            <[T; 14] as std::default::Default>
            <&'a [T] as std::default::Default>
            <[T; 22] as std::default::Default>
            <[T; 7] as std::default::Default>
          and 31 others
  = note: required by `std::default::Default::default`

If I attempt to add the missing initializer for the array:

impl Default for [u32; 256] {
    fn default() -> [u32; 255] {
        [0; 256]
    }
}

I get:

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
 --> src/lib.rs:7:5
  |
7 |     impl Default for [u32; 256] {
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ impl doesn't use types inside crate
  |
  = note: the impl does not reference any types defined in this crate
  = note: define and implement a trait or new type instead

Am I doing something wrong?

Rajiv answered 21/11, 2014 at 13:49 Comment(1)
Rust uses macro_rules! to generate Default for arrays up to length 32. But now that const generics are stable, one would assume that at some point they'll go back and impl<const N: usize, T: Default> Default for [T; N]. Unfortunately, for the moment this is blocked on [T; 0] not requiring T: Default while [T; N] does for N >= 1, and there is no “const specialization” yet.Kantor
M
17

Rust does not implement Default for all arrays because it does not have non-type polymorphism. As such, Default is only implemented for a handful of sizes.

You can, however, implement a default for your type:

impl Default for Histogram {
    fn default() -> Histogram {
        Histogram {
            sum: 0,
            bins: [0; 256],
        }
    }
}

Note: I would contend that implementing Default for u32 is fishy to start with; why 0 and not 1? or 42? There is no good answer, so no obvious default really.

Mohler answered 21/11, 2014 at 13:56 Comment(3)
I only got on Default because Zero is labeled as deprecated in the standard library. In the absence of a ~Zero trait, I'd argue that fixed size arrays are more likely to be used in vector-vector addition than anything else, and the zero vector is the identity vector for addition and is a more natural choice for default. But this is some serious bikeshedding we're doing here :)Rajiv
Hi, will this cause a big cost? I am a new learner, and think the [0;256] will be firstly created and initialized in one place of the stack/heap, and then move into the Histogram.bins. Thus the big array is moved which consists of copying and dropping. Thanks!Overwhelming
@ch271828n: As far as the language is concerned, you are right. Once the optimizer gets its teeth on the code however, things will change a lot. First of all, there's no need to Drop the array, because none of its elements require a Drop. Secondly, it's actually likely that the compiler will just write 0 all over Histogram without initializing the elements piecemeal. And thirdly, depending on the caller, it may never actually materialize the struct, and maybe not even materialize the array... optimizing compilers can make astonishing transformations, if given the opportunity.Mohler
L
6

I'm afraid you can't do this, you will need to implement Default for your structure yourself:

struct Histogram {
    sum: u32,
    bins: [u32; 256],
}

impl Default for Histogram {
    #[inline]
    fn default() -> Histogram {
        Histogram {
            sum: 0,
            bins: [0; 256],
        }
    }
}

Numeric types have nothing to do with this case, it's more like problems with fixed-size arrays. They still need generic numerical literals to support this kind of things natively.

Laxation answered 21/11, 2014 at 13:56 Comment(2)
Thanks Vladimir! Matthieu gave the same answer faster, but yours has the inline. I'd accept both if I could!Rajiv
Yeah, I pressed submit button just at the moment when the other answer has appeared :) That's ok, everything is fair :)Laxation
L
4

If you're sure to initialize every field with zero, this would work:

impl Default for Histogram {
    fn default() -> Histogram {
        unsafe { std::mem::zeroed() }
    }
}
Lobule answered 28/3, 2015 at 12:36 Comment(0)
G
2

If the array type is not Copy, the answer by @MatthieuM. won't work. In this case, instead of specifying each element explicitly (which is tedious and even impossible for generic array), you can use std::array::from_fn():

impl Default for Histogram {
    fn default() -> Histogram {
        Histogram {
            sum: Default::default(),
            bins: std::array::from_fn(|_| Default::default()),
        }
    }
}

You can also do it using array::map(), if you prefer:

impl Default for Histogram {
    fn default() -> Histogram {
        Histogram {
            sum: Default::default(),
            bins: [(); 256].map(|()| Default::default()),
        }
    }
}
Goshen answered 3/1, 2024 at 8:36 Comment(0)
R
1

Indeed, at the time of writing, support for fixed-length arrays is still being hashed out in the standard library:

https://github.com/rust-lang/rust/issues/7622

Rajiv answered 21/11, 2014 at 14:0 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.