Nitpick Memory System Specification

Nitpick’s memory model enforces strict lifecycle constraints but offers flexible paradigms—from high-level, garbage-collected/RAII allocations to absolute raw manual management (wild) tailored for systems programming and JIT engines.

1. Allocation Contexts & Modifiers

Variables in Nitpick exist in one of several allocation states. You can use contextual keywords (like stack, gc, wild) immediately before the type declaration to explicitly control their residency.

1.1 Default Managed Memory (Implicit GC/RAII)

If no keyword is provided, the allocation is tracked and managed. The compiler will automatically clean up the binding when it falls out of scope or crosses its NLL (Non-Lexical Lifetime) last-use point.

int32:x = 42i32;           // Automatically managed
int8->:buf = alloc(16i64); // Automatically tracked and freed

1.2 stack

Forces explicit allocation onto the hardware call stack. Extremely fast (just a pointer bump), with memory reclaimed exactly at the scope’s exit.

stack int32:counter = 0i32;

1.3 gc

Explicitly flags the object to be managed by the Garbage Collector, useful for overriding default heuristics on complex structs or when returning references.

gc MyStruct:obj = ...;

1.4 wild (Unmanaged / Manual Memory)

Declares the memory as completely unmanaged. wild pointers must be manually freed. They explicitly bypass RAII and GC tracking. If you fail to free a wild pointer, the compiler will abort with [NITPICK-014] Memory leak.

wild int8->:buffer = alloc(1024i64);

1.5 wildx (Executable Memory)

Reserved for JIT compilers. Provides executable memory adhering to W⊕X (Write XOR Execute) security protocols with ASLR and guard pages.

wildx uint8->:code = wildx_alloc(4096i64);

2. Managing wild Memory

Because wild memory escapes automatic lifecycle tracking, it introduces the risk of leaks and use-after-free conditions. Nitpick uses static analysis to prevent leaks at compile time.

2.1 The defer Block

The canonical method for cleaning up wild memory is using a defer block immediately following the allocation. This guarantees the free runs on every exit path (including early return, pass, fail, or exit).

wild int8->:buf = alloc(16i64);
defer { dalloc(buf); }

2.2 nodrop

The nodrop keyword acts as a per-binding RAII opt-out. It prevents an outer initializer from being hooked into the auto-drop tracker. Useful if you want to allocate something normally tracked but explicitly assume manual wild lifecycle control over it.

wild int8->:manual_buf = nodrop alloc(16i64);

3. Allocation Built-ins (NitpickAlloc)

Nitpick provides raw, slab-backed compiler intrinsics for dynamic sizing. All return wild int8-> and must be handled appropriately. * alloc(size): Allocate size uninitialized bytes. * calloc(count, size): Allocate zero-initialized memory. * ralloc(ptr, new_size): Resize the allocation. Old pointer becomes invalid. * realloc(ptr, new_size): Legacy alias for ralloc. * dalloc(ptr): Explicitly deallocate. * free(ptr): Legacy alias for dalloc.

4. Handle<T> and Arena Allocators

For performance-critical code where you still want memory safety (e.g., Physics Nodes, ECS structures), use Arenas with Handle<T>.

When an object is allocated in an arena, the system issues a Handle<T> containing a generation counter. If the memory slot is reallocated or freed, the generation counter increments. Attempting to use the old handle immediately fails safely by returning an ERR through the Result<T> system, completely eliminating silent use-after-free catastrophes.