Uninitialized Memory

Rust allows developers to work with uninitialized memory. All memory that is allocated during runtime is uninitialized at first, and it will contain garbage values. Any novice programmer will know that working with this memory will cause undefined behavior. Regardless, Rust provides ways of working with uninitialized memory in safe and unsafe ways.

Checked

Rust by default doesn’t allow access to a memory segment that has not been initialized yet. This is great for Java-Rust bindings because it ensures that even if an attempt is made to access uninitialized memory from the Java side (which would normally be allowed and would produce undefined behavior) that is being allocated with Rust through the FFM API, it won’t produce undefined behavior or retrieve garbage values.

Drop Flags

This is related to the concept of lifetimes. Whenever a variable goes out of scope, suppose a variable named x defined as let mut x = Box::new(0);, Rust assigns the drop flag, which then pushes the drop function, drop(x), on the stack. The concept of ownership applies here too, where there can be only one pointer to a memory segment. Drop flags are tracked on the stack, and Rust decides when to drop a value during runtime. This is relevant to creating bindings, because even though Rust may have dropped a value, the Java variable that points to it when using the FFM API usually would not know that happened. Having access to a drop flag allows for tracking when such behavior happens, so they can be invalidated from the Java side too.

Unchecked

Arrays can not be partially initialized, since null does not exist in Rust, so arrays that are defined have to be fully initialized, with a value to every section of memory that is represented by the indexes. This can make developing code harder, especially when trying to work with dynamically allocated arrays. To solve this, Rust implements the MaybeUninit type. For example, to define an array that may be uninitialized, we would write:

let mut x: [MaybeUninit<Box<u32>>; SIZE] = unsafe {
    MaybeUninit::uninit().assume_init() };
}

This works because the MaybeUninit is the only type that can be partially initialized, and .assume_init() makes the Rust compiler think that the array of MaybeUninit<T> was fully initialized. In this case, we are pointing to a Box, which is a container for the type u32. The array can then be initialized with the following:

for i in 0..SIZE {
    x[i] = MaybeUninit::new(Box::new(i as u32));
}

Usually, when working with an array of pointers, assigning a new value to x[i] would mean that the left hand side value would be dropped. But this is not a problem when the left hand side contains MaybeUninit<Box<u32>> because it does not contain anything, it just works as a placeholder. Finally, that array that may be uninitialized may be turned into an array that we know has been uninitialized with this line of code:
unsafe { mem::transmute::<_, [Box<u32>; SIZE]>(x) }

To learn more about checked uninitialized memory, it is recommended to read the official Rust resource The Rustonomicon chapter 5.1.