Out Parameters

Design Decision

GX does not allow returning pointers from GX functions (extern functions are exempt). Instead, GX uses out parameters for mutation and multi-return patterns. All pointer parameters without out are read-only (const in generated C).

Rules

DeclarationMeaningC output
p: *TRead-only pointer (borrowing)const T* p
out p: *TMutable pointer (writing)T* p
out x: TOut parameter (multi-return)T* x (auto & at call site)
self in exMutable (special case)T* self

Why No Pointer Returns?

In real systems code, the caller allocates and the caller cleans up. Functions receive pointers and either read or write through them. This makes ownership explicit and eliminates “who frees this?” bugs.

// Caller allocates, caller owns, caller cleans up
fn main() {
    var arena = Arena{}
    defer { arena.reset() }

    var player:*Player = arena.alloc(Player)
    init_player(out player, "Alice")    // fills it in
    update_player(out player, 0.016)    // modifies it
    render_player(player)               // reads it (const)
}

Exemptions

  • extern fn may return pointers (C interop: malloc, fopen, etc.)
  • Built-in allocator methods (.alloc(T)) return *T
  • self in extension methods is always mutable

Multiple Return Values

Use out parameters instead of returning structs or tuples:

fn parse_int:bool(input: str, out value: i32, out consumed: i32) {
    value = 42
    consumed = 2
    return true
}

var v: i32
var n: i32
if (parse_int("42abc", v, n)) {
    print("Got {v.str()}")
}

Future Consideration

context<T> — a fat pointer carrying allocator info ({ptr: *T, alloc: *void}) was considered for cases where allocation happens inside called functions. Deferred because the common pattern (allocate at top, pass down) doesn’t need it.