Initializing an array of structs

Hi there,

I am looking for an easy way to initialize an array of structs (I do not want to type out, as it is pretty large). I isolated my problem to this code:

#[derive(Debug)]
struct TestStruct {
    a: i32,
    b: f32,
    c: char,
    d: bool
}

impl TestStruct {
    fn new() -> TestStruct {
        TestStruct {a: 1, b: 1.0, c: 'c', d: true}
    }
}

fn main() {
    let test_var = TestStruct::new();

    let test_array = [TestStruct::new(); 20];
    println!("test_var: {:#?}", test_var);
    println!("test_array: {:#?}", test_array);
}

The code does not compile, error is, that there is no copy trait for TestStruct. Two questions:

  1. Why do I need a copy trait? I actually want it to call 20 times the new function!?
  2. I also failed to find information how to actually implement the copy trait, I only found clone, is it the same?

Thanks & regards

PS: Answer to question 1) might be this:
= note: the Copy trait is required because the repeated element will be copied
(Seems the way how Rust implements that syntax above, obviously it indeed creates it once, and then copies it.... than I would change question 1 to: Is there another/better way to do this?

1 Like

When you write an array initializer like [x; 20], it effectively expands into code like...

let copy = x;
[copy, copy, copy, copy, ... 20 times]

Because the result of evaluating x is used more than once, you need to be able to duplicate it. In the case of an array, that duplication can only be done with Copy -- the compiler is not willing to generate hidden calls to Clone, which might be expensive, in this case.

You've encountered one of the annoying things about arrays in Rust. Primitive arrays like [TestStruct; 20] often feel like second-class citizens of the language, in my experience.

To get around this problem without implementing Copy, I suggest the following:

Don't use an array, use a Vec. If you implement Clone, the macro vec![TestStruct::new(); 20] will work -- Vec is willing to Clone things, unlike array. But you say you actually want new() called 20 times, instead of Clone-ing the result -- Rust won't do that behind the scenes, but you can ask for it by making an empty Vec and push into it:

let mut test_vec = Vec::with_capacity(20);
for _ in 0..20 {
    vec.push(TestStruct::new());
}

Now, it is possible to create a [TestStruct; 20] that is uninitialized memory, and fill it in one element at a time by repeatedly calling new() -- basically what you want the compiler to do for you, if I understand correctly. But it requires some slightly more complicated code using unsafe, and it's not what I would do.

1 Like

If you're willing to use iterators, you can use std::iter::repeat_with to make an infinite sequence with the results of applying a closure repeatedly, from which you can take a finite amount and collect into a Vec.

let v = std::iter::repeat_with(|| TestStruct::new())
    .take(20)
    .collect::<Vec<_>>();

If you really want some fixed size non-allocating array, the arrayvec::ArrayVec<[T; N]> can be both .collect()ed and .into_inner()ed to actual array.

Thanks for the replies so far!

So, using a vector is not an option I think, reason is that this data structure needs to be exchanged with a C/C++ interface in a DLL, and for that I need the continous memory, I think.

So the arrayvec seems to be an option. But I thought again about the copy trait. Problem I did not understand the documentation for it, as I did not understand how to implement it. I did not expect to take it that literal. So got it working with this:

#[derive(Debug)]
struct TestStruct {
    a: i32,
    b: f32,
    c: char,
    d: bool
}

impl Copy for TestStruct {}

impl Clone for TestStruct {
    fn clone(&self) -> TestStruct {
        TestStruct{a: self.a, b: self.b, c: self.c, d:self.d}
    }
}

impl TestStruct {
    fn new() -> TestStruct {
        TestStruct {a: 1, b: 1.0, c: 'c', d: true}
    }
}

fn main() {
    let test_var = TestStruct::new();

    let test_array = [TestStruct::new(); 20];
    println!("test_var: {:#?}", test_var);
    println!("test_array: {:#?}", test_array);
}

A Vec will allocate a continuous piece of memory on the heap, so unless you also need it to be on the stack, it should be fine?

1 Like

Right -- the only advantage the array gives you is stack allocation, and that may well be a disadvantage if the array grows large. You can get a reference to the Vec's internal contiguous memory by calling vec.as_slice() or &vec[..] (a matter of aesthetics), and you can get a C-style pointer from the slice by following that up with .as_ptr().

In most cases you don't implement clone and copy by hand in rust, instead use the derive macros provided:

#[derive(Clone,Copy,Debug)]
struct TestStruct{
...
}

This works as long as every field in the struct implement the corresponding trait.

The ::array-init crate is made precisely for this:

use ::array_init::array_init;

let test_array: [TestStruct; 20] = array_init(|_| TestStruct::new());
4 Likes

Please, read the docs.

Struct std::vec::Vec

A contiguous growable array type, written Vec<T> but pronounced 'vector'.

2 Likes

Thanks, all very helpful as I I learnt a lot!

The reason that I was not using the derive macros, was because I am also using the repr(C) macro and I simply did not know how to combine them.... found that (simply write them after each other).

FYI, that's just an attribute, not a macro.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.