Generics

A generic class or struct takes type parameters and constant parameters. Its body is recorded once and replayed per set of arguments, so every instance is an ordinary type with a layout of its own and a constant the compiler folded.


Declaring #

class Name<T[, U...][, const N: i64]> { members }
struct Name<...> { members }

A type parameter stands wherever a type word stands inside the body — a field, a parameter, a return type, a local, the element of an inline array. A const parameter is an i64 and stands wherever a constant stands, which is what makes T items[N] a real inline array whose length the instantiation fixes.

Instantiating #

An instance is born at its first use — a declaration, a field or parameter type, a return type, or new — and it is memoized by (name, arguments), so Box<Circle, 2> names one type however many times it is written. Nesting closes on a >> without a space.

writtenthe type
Box<Circle, 4> b = new Box<Circle, 4>;Box__Circle__4
Holder<Box<Circle, 2>> h = ...;Holder__Box__Circle__2

The instance's own layout constants carry the mangled name: BOX__CIRCLE__4_SIZE, BOX__CIRCLE__2_COUNT. A const argument is an integer literal or a const already declared — nothing else.

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

const i64 TWO = 2;

class Circle {
    public i64 r;
}

class Box<T, const N: i64> {
    T items[N];
    i64 count;

    public i64 cap() {
        return N;                                // an integer literal in this instance
    }

    public i64 add(T it) {
        if (count >= N) {
            return 0;
        }
        this.items[this.count] = it;             // an inline array: through the receiver
        count = count + 1;
        return 1;
    }

    public i64 total() {
        i64 s = 0;
        i64 i = 0;
        loop {
            if (i >= count) break;
            T c = this.items[i];
            s = s + c.r;
            i = i + 1;
        }
        return s;
    }
}

class Holder<T> {
    public T inner;

    public T get() {
        return inner;
    }
}

struct Slot<T, const N: i64> {
    public T v[N];

    public i64 room() {
        return N;
    }
}

i64 main() {
    Box<Circle, 4> b4 = new Box<Circle, 4>;
    Box<Circle, TWO> b2 = new Box<Circle, TWO>;  // a declared const as the argument
    Holder<Box<Circle, TWO>> h = new Holder<Box<Circle, TWO>>;
    Slot<i64, 3> s = new Slot<i64, 3>;

    if (BOX__CIRCLE__2_SIZE != 40) return 1;     // 16 header + 2*8 items + 8 count
    if (BOX__CIRCLE__4_SIZE != 56) return 2;     // one body, four slots
    if (BOX__CIRCLE__2_COUNT != 32) return 3;    // the field after the array moves
    if (SLOT__I64__3_SIZE != 24) return 4;       // a struct: no header at all

    Circle p = new Circle;
    p.r = 20;
    Circle q = new Circle;
    q.r = 21;
    if (b2.add(p) != 1) return 5;
    if (b2.add(q) != 1) return 6;
    Circle x = new Circle;
    x.r = 7;
    if (b2.add(x) != 0) return 7;                // the constant closed this box

    h.inner = b2;
    if (h.get().total() != 41) return 8;

    s.v[0] = 1;
    if (s.room() != 3) return 9;

    return b4.cap() - b2.cap() + h.get().total() - s.v[0] + s.room() - 3;
}

What the replay buys #

N is a constant inside the instance, so an index written as a literal is checked against it at compile time and costs no run-time guard: items[4] in a Box<Circle, 2> is teko: index 4 is out of range for items[2] where it is written. A computed index keeps its guard.

Two instances of one declaration are two types: they share no field, no method symbol and no layout, and Box<Circle, 2> never converts to Box<Circle, 4>.


partial generics #

A generic is partial by the same rule an ordinary class is: one recorded region per part, replayed as the parts were written.

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

partial class Cell<T, const N: i64> {
    public T items[N];
}

partial class Cell<T, const N: i64> {
    public i64 cap() {
        return N;
    }
}

i64 main() {
    Cell<i64, 3> c = new Cell<i64, 3>;
    c.items[0] = 39;
    return c.items[0] + c.cap();
}

Limits #

limitvalue
a const generic parameteris an i64
a const generic argumentan integer literal or a declared const
a qualified generic (geo.Box<i64>)not taught; a generic declared in a namespace keeps its short name
a generic delegatenot taught
a generic as a dependency-injection key (IRepo<T>)not taught
generic declarations in one source16
parameters of one generic4
instances generated in one source32
recorded parts, summed across all generics32

An argument that names no known type is teko: unknown type as a generic argument; a < on a type that declares no parameters is teko: not a generic type. The full list is diagnostics.md.

Edit this page