Reputation: 106790
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.
Upvotes: 0
Views: 621
Reputation: 26757
It's undefined behavior, (What Every C (Rust using unsafe also) Programmer Should Know About Undefined Behavior):
Behavior considered undefined
- A reference or Box that is dangling, unaligned, or points to an invalid value.
Note:
Undefined behavior affects the entire program. For example, calling a function in C that exhibits undefined behavior of C means your entire program contains undefined behaviour that can also affect the Rust code. And vice versa, undefined behavior in Rust can cause adverse affects on code executed by any FFI calls to other languages.
Dangling pointers
A reference/pointer is "dangling" if it is null or not all of the bytes it points to are part of the same allocation (so in particular they all have to be part of some allocation). The span of bytes it points to is determined by the pointer value and the size of the pointee type (using size_of_val). As a consequence, if the span is empty, "dangling" is the same as "non-null". Note that slices and strings point to their entire range, so it is important that the length metadata is never too large. In particular, allocations and therefore slices and strings cannot be bigger than isize::MAX bytes.
Upvotes: 2