4

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()?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Piotr
  • 91
  • 1
  • 4
  • 5
    even if rust allow this kind of thing for variable, I would advice to never user this feature. Initialize your variable with the correct value in the first place, this make the code way easier to read, and less error prone. You need to have a strong reason to not initialize your variable when you declare them – Stargateur Nov 18 '20 at 00:57

3 Answers3

4

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
0

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 }];
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jonas
  • 121,568
  • 97
  • 310
  • 388
0

Rust requires that every element in an array is initialized. Because Rust does not know if the elements in p were initialized, the compiler statically prevents you from using them.

Rust does not let you use null (in safe code). It does this to prevent undefined behavior. If Rust let you use the possibly uninitialized array p, this could potentially cause a whole lot of runtime errors.

In your case, elements in the p may either be initialized, or uninitialized. This looks like a perfect case for Option. An Option<T> can either be Some(T), or None:

let mut p: [Option<Point>; 2] = [None, None];

p[0] = Some(Point { x: 1, y: 2 });
p[1] = Some(Point { x: 2, y: 3 });
Ibraheem Ahmed
  • 11,652
  • 2
  • 48
  • 54