Given a struct like so:
pub struct MyStruct<'a> {
id: u8,
other: &'a OtherStruct,
}
I want to partially initialize it with an id
field, then assign to other
reference field afterwards. Note: For what I'm showing in this question, it seems extremely unnecessary to do this, but it is necessary in the actual implementation.
The rust documentation talks about initializing a struct field-by-field, which would be done like so:
fn get_struct<'a>(other: &'a OtherStruct) -> MyStruct<'a> {
let mut uninit: MaybeUninit<MyStruct<'a>> = MaybeUninit::uninit();
let ptr = uninit.as_mut_ptr();
unsafe {
addr_of_mut!((*ptr).id).write(8);
addr_of_mut!((*ptr).other).write(other);
uninit.assume_init()
}
}
Ok, so that's a possibility and it works, but it it necessary? Is it safe to instead do the following, which also seems to work?
fn get_struct2<'a>(other: &'a OtherStruct) -> MyStruct<'a> {
let mut my_struct = MyStruct {
id: 8,
other: unsafe { MaybeUninit::uninit().assume_init() },
};
my_struct.other = other;
my_struct
}
Note the first way causes no warnings and the second one gives the following warning...
other: unsafe { MaybeUninit::uninit().assume_init() },
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
this code causes undefined behavior when executed
help: use `MaybeUninit<T>` instead, and only call `assume_init` after initialization is done
...which makes sense because if the other
field were accessed that could cause problems.
From having almost no understanding of this, I'm guessing that for the second way it's initially defining a struct that has its other
reference pointing at whatever location in memory, but once a valid reference is assigned it should be good. Is that correct? I'm thinking it might matter for situations like if there was a struct or enum that wasn't initialized due to compiler optimizations so wrapping in MaybeUninit
would prevent those optimizations, but is it ok for a reference? I'm never accessing the reference until it's assigned to.
Edit: Also, I know this could also be solved by using an Option
or some other container for initialization in the private API of the struct, but let's skip over that.