Skip to content

Memory Management

Nic gives you explicit control over memory. There's no garbage collector—you decide when to allocate and deallocate.

Stack vs Heap

Stack allocation is automatic and fast. Variables are cleaned up when they go out of scope:

nic
struct Point {
    x: f64,
    y: f64
}

fn stack_example() -> unit {
    let x: i32 = 42;           // stack allocated
    let arr: [10]i32;          // stack allocated array
    let p: Point = Point { x: 1.0, y: 2.0 };  // stack allocated struct
    
    // All automatically cleaned up when function returns
    return;
}

Heap allocation requires explicit new and release:

nic
fn heap_example() -> unit {
    let p: *Point = new Point { x: 1.0, y: 2.0 };  // heap allocated struct
    
    // Use the value...
    p.x = 10.0;
    
    release p;  // must free explicitly
    return;
}

The new Keyword

new allocates on the heap and returns a pointer:

nic
struct Point {
    x: f64,
    y: f64
}

fn main() -> unit {
    // Allocate struct with field values
    let p: *Point = new Point { x: 1.0, y: 2.0 };
    
    // Allocate class (calls init if defined)
    let c: *Counter = new Counter();
    
    // Allocate with constructor arguments
    let conn: *Connection = new Connection(8080);
    
    release p;
    release c;
    release conn;
    return;
}

The release Keyword

release frees heap memory. For classes, it calls deinit first:

nic
fn main() -> unit {
    let p: *Point = new Point { x: 0.0, y: 0.0 };
    
    // ... use p ...
    
    release p;  // memory freed
    
    // WARNING: using p after release is undefined behavior!
    // p.x = 1.0;  // DON'T DO THIS
    
    return;
}

The defer Statement

defer schedules cleanup to run when the current scope exits:

nic
fn with_defer() -> unit {
    let p: *Point = new Point { x: 1.0, y: 2.0 };
    defer release p;  // will run at end of function
    
    // Safe to use p here...
    p.x = 10.0;
    
    // Even with early return, defer runs
    if p.x > 5.0 {
        return;  // defer release p runs here
    }
    
    // defer release p also runs here
    return;
}

LIFO Execution

Multiple defers execute in reverse order (Last In, First Out):

nic
fn multiple_defers() -> unit {
    let a: *Point = new Point { x: 1.0, y: 1.0 };
    defer release a;  // runs third
    
    let b: *Point = new Point { x: 2.0, y: 2.0 };
    defer release b;  // runs second
    
    let c: *Point = new Point { x: 3.0, y: 3.0 };
    defer release c;  // runs first
    
    // Order: release c, release b, release a
    return;
}

Block-Scoped Defer

Defers run at the end of their enclosing block:

nic
fn block_defer() -> unit {
    let outer: *Point = new Point { x: 0.0, y: 0.0 };
    defer release outer;
    
    {
        let inner: *Point = new Point { x: 1.0, y: 1.0 };
        defer release inner;
        
        // inner released here, at end of block
    }
    
    // outer still valid here
    outer.x = 10.0;
    
    // outer released here
    return;
}

Pointers and References

nic
fn pointers() -> unit {
    let x: i32 = 42;
    
    let ptr: *i32 = &x;    // pointer to x
    let val: i32 = *ptr;   // dereference: 42
    
    *ptr = 100;            // modify through pointer
    // x is now 100
    
    return;
}

Nil Pointers

nic
fn main() -> unit {
    let ptr: *i32 = nil;  // null pointer
    
    // Check before use
    if ptr != nil {
        let val: i32 = *ptr;
    }
    
    return;
}

Common Patterns

Resource Acquisition

nic
fn process_file(path: string) -> Result[string, string] {
    let file: *File = new File(path);
    defer release file;
    
    if !file.exists() {
        return Err("not found");
    }
    
    let content: string = file.read();
    return Ok(content);
    // file released automatically via defer
}

Cleanup on Error

nic
fn safe_operation() -> Result[i32, string] {
    let resource: *Resource = new Resource();
    defer release resource;
    
    let result: Result[i32, string] = resource.do_work();
    
    match result {
        Ok(v) -> return Ok(v),
        Err(e) -> return Err(e)
    };
    // resource released in all cases
}

Memory Safety Rules

  1. Every new needs a release — Memory leaks if you forget
  2. Don't use after release — Undefined behavior
  3. Don't release twice — Undefined behavior (double free)
  4. Use defer for safety — Ensures cleanup even with early returns

Summary

FeaturePurpose
new StructName { ... }Heap allocate struct
new ClassName()Heap allocate class
release pFree memory (calls deinit for classes)
defer stmtRun at scope exit
&xAddress of
*ptrDereference
nilNull pointer

Next

Learn about Lazy Evaluation for deferred computation.

Released under the MIT License.