1.3. MaybeUninit<T> - uninitialized memory
The rok::bare::mem module defines a wrapper for handling uninitialized memory safely, along with the associated ops. Whereas an ordinary T is always guaranteed to be initialized, MaybeUninit<T> expresses initialization status explicitly at the type level.
1.3.1. Motivation - reading uninitialized memory is UB
Under LLVM's freeze semantics (clarified in 2022), reading uninitialized memory is UB even for integer types. It is not merely that a "garbage value" comes out; the compiler may assume anything about the value and transform the code accordingly.
// C
int x; // uninit
if (x == 0) { ... } // the compiler may assume any value for x - either branch may become dead code
T itself therefore cannot represent the uninitialized state, and a separate wrapper is needed.
1.3.2. MaybeUninit<T>
struct MaybeUninit<T> { /* same size + align as T, but possibly uninitialized */ }
impl<T> MaybeUninit<T> {
fn uninit() -> MaybeUninit<T>; // uninitialized slot
fn zeroed() -> MaybeUninit<T>; // filled with 0
fn new(value: T) -> MaybeUninit<T>; // initialized slot
fn write(ref self, value: T); // write value
fn as_ptr(self) -> ConstPtr<T>;
fn as_mut_ptr(ref self) -> Ptr<T>;
unsafe fn assume_init(self) -> T;
// SAFETY: only when initialization is guaranteed by write / an external mechanism
}
as_ptr / as_mut_ptr need the receiver to be a place: the pointer points at that variable, not at a copy. uninit() may be implemented as zeroed().
Example - separating the initialization step
A typical pattern: wait for FFI to fill a buffer, then take the value out.
let buf: MaybeUninit<[u8; 1024]> = MaybeUninit::uninit();
let p: Ptr<u8> = buf.as_mut_ptr().cast::<u8>();
unsafe {
read_from_kernel(p, 1024); // FFI fills buf
let initialized: [u8; 1024] = buf.assume_init();
}
Using after write
write only stores the value; it returns no reference (rok has no ref returns). After writing, take the value out with assume_init.
let slot: MaybeUninit<Big> = MaybeUninit::uninit();
slot.write(big_value());
let v: Big = unsafe { slot.assume_init() };
process(v);
1.3.3. The Pod trait
trait Pod { /* marker */ }
Short for Plain Old Data: a type for which every bit pattern is valid. The reinterpret of 1.2 cast requires this marker.
| Automatic impl | Not applied |
|---|---|
| All integers (i*, u*) | Ptr<T> (provenance) |
| All floating-point (f*) | User structs (explicit #[derive(Pod)]) |
[T; N] (T:Pod) | enum (variant tag - niche) |
(T, U, ...) (all Pod) | |
c_char | |
bool, char (invalid bits handled by automatic niche) |
What Pod guarantees
Pod guarantees three things at once.
- Reading any bit pattern is safe - no uninit exposure.
- Deterministic layout:
#[repr(C)]or#[repr(transparent)]is required. - No Drop: if
Dropis implemented,Podis automatically rejected.
#[derive(Pod)]
#[repr(C)]
struct Vec3 { x: f32; y: f32; z: f32; }
// → reinterpret OK, ffi safe, etc.
1.3.4. raw mem ops - read / write
The free functions for low-level memory manipulation are:
unsafe fn read<T>(src: ConstPtr<T>) -> T;
unsafe fn write<T>(dst: Ptr<T>, value: T);
unsafe fn read_volatile<T>(src: ConstPtr<T>) -> T;
unsafe fn write_volatile<T>(dst: Ptr<T>, value: T);
unsafe fn copy<T>(src: ConstPtr<T>, dst: Ptr<T>, count: usize);
// memmove (overlap OK)
unsafe fn copy_nonoverlapping<T>(src: ConstPtr<T>, dst: Ptr<T>, count: usize);
// memcpy (overlap UB - faster)
unsafe fn write_bytes<T>(dst: Ptr<T>, value: u8, count: usize);
// memset (every byte = value)
unsafe fn swap<T>(a: Ptr<T>, b: Ptr<T>);
unsafe fn replace<T>(dst: Ptr<T>, value: T) -> T; // read existing + write
unsafe fn drop_in_place<T>(p: Ptr<T>); // runs the drop glue (does not free memory)
Most of these are also exposed as methods of Ptr<T> (1.1.2); the free functions of this module serve as an ergonomic alternative.
drop_in_place runs the drop glue: the type's own drop, then its fields' in reverse declaration order (2.13.4).
1.3.5. transmute_unchecked - escape hatch
It behaves the same as in 1.2 cast and is also exposed from this module as an alias.
unsafe fn transmute_unchecked<A, B>(a: A) -> B; // no size check
1.3.6. align / size util
fn align_of<T>() -> usize; // T::align()
fn size_of<T>() -> usize; // T::size()
fn align_of_val<T>(val: T) -> usize;
fn size_of_val<T>(val: T) -> usize; // takes a value - no unsized types, same as size_of::<T>()
fn min_align_of<T>() -> usize; // same as align_of - Rust compatibility
These are not unsafe. They are effectively free-function forms of the Reflect trait (2.12 reflect), also exposed in the rok::bare::mem module for convenience.
1.3.7. intrinsic mapping
The correspondence between each item and compiler intrinsics is:
| Item | intrinsic |
|---|---|
MaybeUninit<T> | rok.mem.assume_init (no-op) |
Pod | (marker - derived automatically) |
read / write | rok.mem.read.T |
copy / copy_nonoverlapping | rok.mem.memcpy / memmove |
write_bytes | rok.mem.memset |
1.3.8. Comparison with other languages
| Aspect | C | Rust | Zig | rok |
|---|---|---|---|---|
| Uninitialized representation | T x; (silent) | MaybeUninit<T> | undefined | MaybeUninit<T> |
Pod / safe transmute | none | bytemuck::Pod (lib) | @bitCast | Pod + reinterpret |
| Uninitialized read UB | allowed | UB | UB | UB |
| memcpy / memmove | memcpy | ptr::copy* | @memcpy | rok::bare::mem::copy* |