Arrays

Two array shapes, and they are different types. A fixed array has a length the source writes down and lives where it is declared; a heap array T[] is an object with a length it carries, allocated by new T[n] and reference-counted.


Fixed arrays #

T a[N];              // a local, a global, or a field
T t[] = { … };       // a global with an initializer

N is an integer literal or a const, and it is positive. The element type is a scalar — an array of objects is refused (teko: an array of objects is not taught yet); use T[].

writtenon a localon a globalon a field
a[i]yesyesthrough the receiver: this.items[i]
a[i] = e, +=, -=, ++, --yesyesthrough the receiver
a.Lengthyes——

A literal index outside [0, N) is a compile-time error, teko: index K is out of range for a[N]; an index computed at run time on a fixed array is not guarded. Width and sign follow the element type: u8/u16/u32 read back zero-extended, i32 sign-extended.

// expect-exit: 42
#include "rt.tk"

u8  g[8];
i64 t[] = { 1, 2, 3, 4, 5 };

i64 sum_local() {
    i64 a[4];
    for (i64 i = 0; i < a.Length; i++) {
        a[i] = i * 2;
    }
    a[0] += 100;
    a[0] -= 100;
    i64 total = 0;
    for (i64 i = 0; i < a.Length; i++) {
        total = total + a[i];
    }
    return total;                                // 0 + 2 + 4 + 6
}

i64 sum_global() {
    i64 total = 0;
    for (i64 i = 0; i < 8; i++) {
        g[i] = i;
        total = total + g[i];
    }
    for (i64 i = 0; i < 5; i++) {
        total = total + t[i];
    }
    return total;                                // 28 + 15
}

i64 signs() {
    i32 s[2];
    s[0] = 0 - 5;
    if (s[0] >= 0) return 1;                     // read back sign-extended
    return 0;
}

class Grid {
    public i64 product3() {
        i64 xs[3];
        xs[0] = 2;
        xs[1] = 3;
        xs[2] = 7;
        i64 p = 1;
        for (i64 i = 0; i < xs.Length; i++) {
            p = p * xs[i];
        }
        return p;
    }
}

i64 main() {
    if (sum_local() != 12) return 1;
    if (sum_global() != 43) return 2;
    if (signs() != 0) return 3;
    Grid gr = new Grid();
    return gr.product3();
}

An inline array field #

A field written T items[N] is part of the object's own layout — the size grows with N, and the field after it moves. It is reached through the receiver written out (this.items[i] inside the type, b.items[i] on a local), never by the bare name.


Heap arrays: T[] #

T[] xs = new T[n];       // n is any expression

T[] is an object: a vtable, a reference count, its Length, then the elements. It is a type in every position — a local, a global, a field, a parameter, a return type — and it is counted like a class reference.

writtenmeans
new T[n]n elements, zeroed; a negative n panics
xs[i] / xs[i] = e / += / -= / ++ / --element access, guarded at run time
xs.Lengththe length it carries
xs = nullreleases the array, and every element still in it

Every index is checked: out of range is a panic with exit 70 (teko: index past the end of an array), never a read past the allocation.

// expect-exit: 42
#include "rt.tk"

class Box {
    i64[] items;                                 // as a field

    public Box(i64 n) {
        this.items = new i64[n];
    }

    public void fill(i64[] src) {                // as a parameter
        i64 i = 0;
        while (i < src.Length) {
            this.items[i] = src[i];
            i = i + 1;
        }
    }

    public i64 total() {
        i64 s = 0;
        i64 i = 0;
        while (i < this.items.Length) {
            s = s + this.items[i];
            i = i + 1;
        }
        return s;
    }
}

delegate i64 Op(i64 a, i64 b);

i64 add(i64 a, i64 b) { return a + b; }
i64 mul(i64 a, i64 b) { return a * b; }

i64 main() {
    i64 n = 5;
    i64[] xs = new i64[n];
    for (i64 i = 0; i < xs.Length; i++) {
        xs[i] = i;
    }
    xs[0] += 100;
    xs[2]++;
    if (xs[0] != 100) return 1;
    if (xs[2] != 3) return 2;

    u8[] bs = new u8[2];
    bs[0] = 250;
    bs[1] = 10;
    if (bs[0] + bs[1] != 260) return 3;          // widened on the way out

    i64[] src = new i64[3];
    src[0] = 1;
    src[1] = 2;
    src[2] = 3;
    Box b = new Box(3);
    b.fill(src);
    if (b.total() != 6) return 4;

    Op[] ops = new Op[2];                        // an array of delegate values
    ops[0] = add;
    ops[1] = mul;
    Op picked = ops[1];
    if (picked(3, 4) != 12) return 5;

    return xs[0] - b.total() - 52;                // 100 - 6 - 52
}

An element of delegate type is called like any other delegate value: read it into a name, or write an argument list straight after the index — the element ops[i] followed by (3, 4) is the call.

Elements of a counted type #

An element of class, interface, delegate or array type holds a reference: overwriting a slot releases what was there, and releasing the array releases every element still in it.

// expect-exit: 42
#include "rt.tk"

i64 dtors = 0;

class Circle {
    i64 r;

    public Circle(i64 r) {
        this.r = r;
    }

    public i64 area() {
        return r * r;
    }

    ~Circle() {
        dtors = dtors + 1;
    }
}

i64 sum_areas(Circle[] cs) {
    i64 total = 0;
    i64 i = 0;
    while (i < cs.Length) {
        total = total + cs[i].area();
        i = i + 1;
    }
    return total;
}

i64 main() {
    Circle[] cs = new Circle[3];
    cs[0] = new Circle(1);
    cs[1] = new Circle(2);
    cs[2] = new Circle(3);
    if (rt_live() != 4) return 1;                // the array object and the three circles
    if (sum_areas(cs) != 14) return 2;

    cs[0] = new Circle(10);                      // the slot released Circle(1)
    if (dtors != 1) return 3;

    cs = null;                                   // the array, and the three live elements
    if (dtors != 4) return 4;
    if (rt_live() != 0) return 5;
    return 42;
}
// expect-exit: 70
#include "rt.tk"

i64 main() {
    i64[] xs = new i64[3];
    return xs[5];                                // teko: index past the end of an array
}

A global T[] #

i64[] g; at top level declares the reference with no length; g = new i64[n]; fills it later, and g[i], the compound forms and g.Length all answer. A global is a root: it is never released, so rt_live() never returns to its floor once one has been filled (memory.md).


Iterating #

foreach (T x in xs) walks a heap array — a local or a parameter — a local fixed array or an inline array field; the element type may widen, never narrow. A global T[] is not a foreach source. It is control-flow.md § foreach.


Limits #

limitvalue
T[][], or any multidimensional arraynot taught (an array of arrays is not taught yet)
a fixed array of a class or struct typenot taught; use T[]
reading a ref T[] / out T[] inside the calleenot taught: the parameter carries the caller's slot, so xs[i] and xs.Length there are refused (expression with no codegen)
.Length on a global fixed arraynot taught; a local fixed array and any T[] answer
an inline array field through a parameter (p.items[i])not taught; a local or this resolves
a run-time index into a fixed arraynot guarded (a T[] index always is)
local array declarations in scope1024
global arrays in one source512
global T[] in one source32

Edit this page