+++ title = "8. Memory Management" weight = 8 +++
Zen C allows manual memory management with ergonomic aids.
Execute code when the current scope exits. Defer statements are executed in LIFO (last-in, first-out) order.
let f = fopen("file.txt", "r");
defer fclose(f);
{% alert(type="warning") %}
To prevent undefined behavior, control flow statements (return, break, continue, goto) are not allowed inside a defer block.
{% end %}
Automatically free the variable when scope exits.
autofree let types = malloc(1024);
Zen C treats types with destructors (like File, Vec, or malloc'd pointers) as Resources. To prevent double-free errors, resources cannot be implicitly duplicated.
- Move by Default: Assigning a resource variable transfers ownership. The original variable becomes invalid (Moved).
- Copy Types: Types without destructors may opt-in to
Copybehavior, making assignment a duplication.
Diagnostics & Philosophy: If you see an error "Use of moved value", the compiler is telling you: "This type owns a resource (like memory or a handle) and blindly copying it is unsafe."
{% alert(type="note") %} Contrast: Unlike C/C++, Zen C does not implicitly duplicate resource-owning values. {% end %}
Function Arguments: Passing a value to a function follows the same rules as assignment: resources are moved unless passed by reference.
fn process(r: Resource) { ... } // 'r' is moved into function
fn peek(r: Resource*) { ... } // 'r' is borrowed (reference)
Explicit Cloning: If you do want two copies of a resource, make it explicit:
let b = a.clone(); // Calls the 'clone' method from the Clone trait
Opt-in Copy (Value Types): For small types without destructors:
struct Point { x: int; y: int; }
impl Copy for Point {} // Opt-in to implicit duplication
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copied. p1 stays valid.
}
Implement Drop to run cleanup logic automatically when a value goes out of scope.
impl Drop for MyStruct {
fn drop(self) {
self.free();
}
}
Wrapping an RAII type. If your struct contains a field that already implements
Drop (like Vec or String), the compiler handles cleanup automatically.
You do NOT need to write impl Drop for the outer struct.
struct MyVecWrapper {
inner: Vec<int>; // Vec::drop() called automatically
}
{% alert(type="note") %}
Example: String is defined as struct String { vec: Vec<char>; } — it
does NOT need an explicit Drop implementation because Vec<char> handles
all the memory. Set<T> on the other hand uses raw T* pointers and
DOES need explicit Drop.
{% end %}
Rule of thumb:
- Fields that are
Vec<T>,String, or other RAII types → automatic Drop - Fields that are raw
T*,malloc'd pointers, or file handles → explicit Drop required