rokdocs
v0.1

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 implNot 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.

  1. Reading any bit pattern is safe - no uninit exposure.
  2. Deterministic layout: #[repr(C)] or #[repr(transparent)] is required.
  3. No Drop: if Drop is implemented, Pod is 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:

Itemintrinsic
MaybeUninit<T>rok.mem.assume_init (no-op)
Pod(marker - derived automatically)
read / writerok.mem.read.T
copy / copy_nonoverlappingrok.mem.memcpy / memmove
write_bytesrok.mem.memset

1.3.8. Comparison with other languages

AspectCRustZigrok
Uninitialized representationT x; (silent)MaybeUninit<T>undefinedMaybeUninit<T>
Pod / safe transmutenonebytemuck::Pod (lib)@bitCastPod + reinterpret
Uninitialized read UBallowedUBUBUB
memcpy / memmovememcpyptr::copy*@memcpyrok::bare::mem::copy*

Edit this page on GitHub