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
| Declaration | Meaning | C output |
|---|---|---|
p: *T | Read-only pointer (borrowing) | const T* p |
out p: *T | Mutable pointer (writing) | T* p |
out x: T | Out parameter (multi-return) | T* x (auto & at call site) |
self in ex | Mutable (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 fnmay return pointers (C interop: malloc, fopen, etc.)- Built-in allocator methods (
.alloc(T)) return*T selfin 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.