-
Notifications
You must be signed in to change notification settings - Fork 0
Memory Model & Ownership
Memory safety without a garbage collector is one of Krait's most powerful production features. This document details Krait's static Ownership Memory Model, Move Semantics, and compiler-driven Auto-Drop mechanism.
This model ensures Krait performs at raw native C speed while guaranteeing complete safety from double-free and use-after-free vulnerabilities.
High-performance programming languages generally manage memory in one of three ways:
- Manual Allocation (C/C++): High speed, but prone to devastating runtime bugs like memory leaks, double-frees, or use-after-free segfaults.
- Garbage Collection (Python/Java/Go): Safe, but suffers from heavy runtime overhead, high memory usage, and unpredictable "stop-the-world" pauses.
- Static Borrow Checker (Rust): Safe and extremely fast, but introduces highly complex syntax, lifetimes annotations, and high visual cognitive noise.
Krait's Solution: A clean, zero-noise compile-time ownership tracker. Krait enforces strict safety checks during compilation and inserts deallocation code automatically, hiding the complexity entirely from the programmer.
To achieve zero-cost memory safety, the Krait compiler enforces three simple rules during semantic analysis:
-
Owner Scope: Every heap-allocated struct instance (created using
new) is owned by exactly one variable. - Move Semantics: Assigning a struct variable to another variable transfers (moves) ownership of the underlying heap pointer. The original variable becomes immediately invalid.
-
Auto-Drop: When a variable owning a heap resource exits its declared scope (e.g., at the end of a block or function), the compiler automatically inserts a native
freeinstruction to deallocate the memory.
In Krait, primitives like integers, floats, and booleans are stored on the stack and are copied by value. However, struct instantiations exist on the heap. When you assign one struct variable to another, ownership is transferred.
make Point
x = 0
y = 0
# 1. p1 is instantiated and owns the Point heap allocation
set p1 = new Point
set p1.x = 100
# 2. Ownership is moved from p1 to p2.
# p2 now owns the heap resource; p1 is marked as "Moved"
set p2 = p1
# 3. Accessing p2 is perfectly safe
show p2.x # Prints 100If you attempt to access a variable after its ownership has been moved, the Krait semantic analyzer detects this and prevents compilation:
set p1 = new Point
set p2 = p1 # Ownership moved to p2
show p1.x # ❌ COMPILE ERROR!When this happens, Krait emits a beautiful, highly actionable diagnostic:
================================================================================
[KRAiT OWNERSHiP ERROR]
================================================================================
Variable: 'p1'
Issue : The variable 'p1' was moved and is no longer valid in this scope.
Why this happens:
Krait employs a high-performance Rust-like Ownership Model with automatic memory deallocation.
When you assign a struct/heap variable to another variable (e.g. `set a = b`), the ownership
of the underlying memory is transferred (moved) to the new variable. The original variable
is invalidated to prevent double-free and use-after-free vulnerabilities at runtime.
How to resolve:
1. Avoid using 'p1' after it has been reassigned/moved.
2. If you need to access 'p1' later, duplicate the data or reorganize your code to assign
the new variable only when you are done using 'p1'.
Memory Safety Guaranteed: Zero-cost abstraction without a garbage collector.
================================================================================
To prevent memory leaks, you do not need to call free or delete manually. The Krait compiler tracks the lifetime of heap owners. When an owner goes out of scope (for instance, at the end of a function or inside a conditional branch block), the LLVM backend automatically emits a native @free call for that pointer.
Consider this Krait function:
make create_and_use()
set temp_point = new Point
set temp_point.x = 42
return temp_point.x
# temp_point goes out of scope here!Behind the scenes, the Krait compiler translates this to optimized LLVM-IR equivalent to the following C logic:
int64_t create_and_use() {
Point* temp_point = (Point*)malloc(sizeof(Point));
temp_point->x = 42;
int64_t ret_val = temp_point->x;
// Auto-Drop generated by compiler!
free(temp_point);
return ret_val;
}This guarantees:
- Zero Memory Leaks: Memory is returned to the operating system immediately when it is no longer needed.
- Zero Double-Frees: Because the compiler invalidates moved variables, a pointer is never freed more than once.
- Optimal Memory Layouts: Clean register utilization and minimal heap fragmentation.