This document defines the intended v1 direction for pointers, allocators, arenas, and ownership in Skunk.
- Keep fixed arrays and structs as value types
- Make heap allocation explicit
- Support allocator-backed single objects and buffers
- Add pointer semantics without exposing unsafe raw memory too early
- Leave room for a later Zig-like custom allocator story
Skunk should distinguish these forms clearly:
TA value of typeT*TA pointer/reference to oneT[]TA non-owning slice/view over manyT[N]TA fixed-size value array containing exactlyNTvalues
Examples:
point: Point; // value
point_ptr: *Point; // pointer/reference to one Point
bytes: []byte; // slice/view over bytes
row: [16]int; // fixed-size value array
- Plain
Tuses value semantics - Allocator-created single objects use
*T - Allocator-created buffers use
[]T
Examples:
function make_point_value(): Point {
return Point { x: 1, y: 2 };
}
function make_point_ref(alloc: Allocator): *Point {
p: *Point = Point::create(alloc);
p.x = 1;
p.y = 2;
return p;
}
function make_bytes(alloc: Allocator, n: int): []byte {
return []byte::alloc(alloc, n);
}
The first pointer form should be:
*T
Examples:
*Point
*int
*[16]byte
Skunk v1 should support:
- pointer types:
*T - pointer-returning allocator APIs
- field access through pointers with automatic dereference
- method calls through pointers with automatic dereference
- assignment through pointer field access
Examples:
p: *Point = Point::create(alloc);
p.x = 10;
print(p.x);
p.set_x(20);
Skunk should reserve explicit dereference syntax for later, but the preferred direction is:
p.*
Reason:
- it avoids overloading prefix
* - it aligns well with a future
*Tsyntax - it keeps field access ergonomic because
p.xcan auto-deref
Skunk v1 should not expose a general address-of operator yet.
That means:
- pointers are produced by allocator/runtime APIs
- pointers are not yet produced freely from arbitrary locals using
&x
Reason:
- allowing
&localimmediately creates lifetime/escape problems - Skunk does not yet have borrow checking or escape analysis
- allocator-created pointers solve the main must-have use case first
Address-of can be added later in an unsafe phase.
Skunk should auto-deref *T for:
- field access
- method calls
Examples:
p: *Point = Point::create(alloc);
p.x = 1;
print(p.x);
p.set_x(3);
This should behave as if the pointer is dereferenced to access the underlying value.
Skunk v1 should not auto-deref everywhere. In particular:
Pointand*Pointare still distinct types- passing
*PointwherePointis expected should not silently copy - passing
Pointwhere*Pointis expected should not silently take an address
Method dispatch should work on both values and pointers:
- calling on
Tlvalues uses the address of the existing storage - calling on
*Tuses the pointer directly - calling on temporaries uses temporary storage
Examples:
point: Point = Point { x: 0, y: 0 };
point.set_x(1); // mutates point storage
ptr: *Point = Point::create(alloc);
ptr.set_x(2); // mutates pointed-to storage
This keeps existing struct method syntax useful after pointer types are added.
Allocator should be a core runtime type, not a user-implemented Skunk type in v1.
Reason:
- it gives Skunk explicit allocation immediately
- it avoids requiring raw memory primitives too early
- it leaves custom allocators for a later
unsafephase
Skunk v1 should also include:
Arena- a process/system allocator entry point
Recommended v1 surface:
heap: Allocator = System::allocator();
p: *Point = Point::create(heap);
heap.destroy(p);
buf: []byte = []byte::alloc(heap, 128);
heap.free(buf);
Recommended built-ins:
System::allocator(): AllocatorT::create(alloc: Allocator): *Talloc.destroy(ptr: *T): void[]T::alloc(alloc: Allocator, len: int): []Talloc.free(buf: []T): void
Optional later additions:
[]T::alloc_fill(alloc, len, value)alloc.clone(buf)alloc.resize(buf, new_len)
Recommended surface:
heap: Allocator = System::allocator();
arena: Arena = Arena::init(heap);
alloc: Allocator = arena.allocator();
p: *Point = Point::create(alloc);
buf: []int = []int::alloc(alloc, 64);
arena.reset();
arena.deinit();
Recommended built-ins:
Arena::init(backing: Allocator): Arenaarena.allocator(): Allocatorarena.reset(): voidarena.deinit(): void
Notes:
resetinvalidates memory allocated from that arenadeinitreleases arena-owned memory- individual
destroy/freethrough an arena allocator may be supported but should not be the preferred style
Returning T returns a value:
function f(): Point {
return Point { x: 1, y: 2 };
}
Semantically this is a value return. The compiler may optimize copies, but the language meaning is value-based.
Returning *T returns a reference:
function f(alloc: Allocator): *Point {
p: *Point = Point::create(alloc);
return p;
}
No value copy is implied. The result refers to the same allocated object.
Returning []T returns a slice header:
function f(alloc: Allocator): []byte {
return []byte::alloc(alloc, 32);
}
The slice header is copied, but the underlying storage is not.
Skunk v1 should use these lifetime rules:
[N]Tis owned by the enclosing value/storage[]Tdoes not own storage*Tdoes not own storage- the allocator or owning value determines lifetime
Programmer responsibility in v1:
- do not use a
*Tafter its allocator has destroyed it - do not use a
[]Tafter its backing storage is freed or reset - do not return or store references to memory whose lifetime has ended
Skunk v1 will likely rely on programmer discipline here rather than a borrow checker.
To implement allocators in Skunk itself, user code would need lower-level memory primitives:
- raw byte pointers
- allocation/reallocation/free at byte granularity
- pointer casts
- pointer offset/arithmetic
- memory copy/set
size_of(T)align_of(T)- probably
unsafe { ... }
That is too much surface area for the current stage of the language.
So the recommended phases are:
- built-in
Allocator - built-in
Arena *T- allocator-created single objects and slices
- explicit dereference
p.* - nullable pointers like
?*T - maybe address-of
&xin restricted or unsafe contexts
- raw memory primitives
unsafe- user-defined allocators implemented in Skunk
When Skunk is ready for user-written allocators, the likely additional surface is:
*byte- many-item/raw pointer forms
&exprp.*size_of(T)align_of(T)mem.copy(dst, src, len)mem.set(dst, byte, len)- raw allocate/reallocate/free hooks
- pointer casts
unsafe { ... }
At that point Skunk could support an allocator interface implemented in Skunk itself.
- Add
*Tas a first-class type - Add built-in
AllocatorandArena - Add
create/destroyand[]T::alloc/free - Auto-deref field/method access for
*T - Add ownership/lifetime docs and compiler diagnostics
- Delay raw pointers and user-defined allocators until later