Arrays, Slices, and Pointers
Sysl distinguishes fixed-size arrays ([n]T, value type), slices ([]T, fat pointer), heap arrays (&[]T, ref-counted), and raw pointers (*T). Each has a distinct role — no hidden indirection.
Fixed arrays
Section titled “Fixed arrays”var arr: [5]int // zero-initialisedarr[0] = 42arr: [3]int = [10, 20, 30] // array literal
// Byte arrays from string and char literalsvar buf: [5]byte = "hello" // copies string bytes into arrayvar msg: [3]byte = ['H', 'i', '!'] // char literals coerce to bytes
// Array decays to pointer when passed to *T parametersum(arr: *int, n: int) -> int = ...sum(myArr, 5) // myArr decays to *intChained indexing
Section titled “Chained indexing”Indexing is a repeatable postfix operator — arr[i][j] works on arrays of arrays, slices of slices, etc.:
val grid = new [3][]int // array of int slicesgrid[0] = row0[:]val v = grid[1][2] // chain: grid[1] returns []int, then [2] indexes it
// Address-of with chained indexval p = &stacks[slot][0] // address of first element of stacks[slot]Dynamic arrays (heap)
Section titled “Dynamic arrays (heap)”a = new [5]int // type: &[]int, ref-counteda[0] = 42len(a) // 5 (from heap header)cap(a) // 5// automatically freed when refcount reaches 0Slices
Section titled “Slices”Slices are fat pointers {ptr, len, cap} (16 bytes). They share the backing storage of the array they were taken from.
a = new [5]ints = a[1:4] // type: []int, shares backing arrays = a[:3] // = a[0:3]s = a[2:] // = a[2:len]s = a[:] // = a[0:len]len(s) // hi - locap(s) // original_cap - loAppend
Section titled “Append”s = a[:0] // empty slice with capacitys = append(s, 42) // returns new slice values = append(s, 99) // Go semantics: may grow if len == capPointers
Section titled “Pointers”x = 42p = &x // p: *int*p = 100 // dereference and assignval y = *p // dereference and read
// Expression lvalues (C-style)(*p).field = 10 // deref pointer, assign field(*p)[i] = 42 // deref pointer, index, assign(arr + 2)[0] = 99 // pointer arithmetic, index, assign
// Pointer arithmetic (scaled by element size)p = &arr[0]val second = *(p + 1) // pointer + offsetp++ // advance by one elementp += 3 // advance by 3 elementsp-- // retreat by one elementp -= 2 // retreat by 2 elements
// Array + offset decays to pointerq = arr + 2 // q: *int (not [n]int)Pointer / ref / value conversions
Section titled “Pointer / ref / value conversions”value → ptr:&vptr → value:*p(explicit; see below)ref → ptr:&r(unsafe — no refcount change)ptr → ref: always an error (cannot manufacture a refcount)
Pointer dereference is explicit
Section titled “Pointer dereference is explicit”Passing *T to a function parameter of type T is a type error. Implicit deref-and-copy was removed because it hides cost: a pointer-passing site that looks like pass-by-reference silently becomes a memcpy of the entire pointee. For a small struct that’s free; for a 4 KB packet it isn’t. Write the deref:
struct Point x: int y: int
sum(p: Point) -> int = p.x + p.y
main() -> int var p = Point(20, 22) val ptr: *Point = &p sum(*ptr) // explicit: sum receives a copy of *ptrThe reverse direction (T → *T) is also not implicit — it would create a dangling pointer to a temporary.
Exception: self in methods
Section titled “Exception: self in methods”Inside a method body self has type *StructName. Passing self to a function that expects the value type auto-derefs, because the method-call sugar already hides the pointer:
Point.total() -> int = sum(self) // self is *Point; sum gets a copy of *selfThis is the only implicit *T → T allowed. Local variables of pointer type, function parameters, struct fields — all require explicit *ptr.
Array / pointer decay rules
Section titled “Array / pointer decay rules”[n]T→*T(array decays to pointer)string→*u8or*i8&T→*U(ref decays to raw pointer)- Any
*T→ any*U(permissive pointer casting)