Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to declare an array of structs and initialize it later?

I have a struct:

struct Point {
    x: u32,
    y: u32,
}

I want to have two Points in two different variables. I want to declare them first and initialize later. It works fine with separate values:

let p0: Point;
let p1: Point;

p0 = Point { x: 1, y: 2 };
p1 = Point { x: 2, y: 3 };

I want to do the same but with an array:

let p: [Point; 2];

p[0] = Point { x: 1, y: 2 };
p[1] = Point { x: 2, y: 3 };

Doesn't work as I get a compilation error:

error[E0381]: use of possibly-uninitialized variable: `p`
 --> src/main.rs:9:5
  |
9 |     p[0] = Point { x: 1, y: 2 };
  |     ^^^^ use of possibly-uninitialized `p`

Why does it behave differently for single variables and arrays? Can I do it without using Default::default()?

like image 279
Piotr Avatar asked Oct 18 '25 13:10

Piotr


2 Answers

Rust requires that every element in an array is initialized. There's literally nowhere for the compiler to track the initialized state of each value.

The by-hand way to do it involves using unsafe Rust code paired with MaybeUninit and raw pointer manipulation. Then the programmer is responsible for correctly upholding all of the requirements:

use std::{mem::MaybeUninit, ptr};

#[derive(Debug)]
struct Point {
    x: u32,
    y: u32,
}

fn main() {
    // I copied this code from Stack Overflow without reading 
    // the prose that describes why this is or is not safe.
    let p = unsafe {
        // Future: MaybeUninit::uninit_array
        let mut p = MaybeUninit::<[Point; 2]>::uninit();

        // Future: MaybeUninit::first_ptr_mut
        let h = p.as_mut_ptr() as *mut Point;
        ptr::write(h.offset(0), Point { x: 1, y: 2 });
        ptr::write(h.offset(1), Point { x: 2, y: 3 });

        p.assume_init()
    };
}

The programmer has to validate that all of the elements have been filled before assume_init is called, otherwise the code has undefined behavior.

Instead, it's much easier to use ArrayVec, which handles all the unsafe logic for you:

use arrayvec::ArrayVec; // 0.5.1

struct Point {
    x: u32,
    y: u32,
}

fn main() {
    let p = {
        let mut p = ArrayVec::<[Point; 2]>::new();

        p.insert(0, Point { x: 1, y: 2 });
        p.insert(1, Point { x: 2, y: 3 });

        p.into_inner()
    };
}

See also:

  • Is there a way to not have to initialize arrays twice?
  • How do I collect into an array?
  • How can I update a value in a mutable HashMap?
  • What is the proper way to initialize a fixed length array?
  • How can I create a stack-allocated vector-like container?
like image 161
Shepmaster Avatar answered Oct 21 '25 05:10

Shepmaster


Arrays are fixed size in Rust and Rust requires that every element in an array is initialized - e.g. no undefined values.

In your case, I would do:

let p: [Point; 2] = [Point { x: 1, y: 2 }, Point { x: 2, y: 3 }];
like image 23
Jonas Avatar answered Oct 21 '25 03:10

Jonas