docs / guide / The Cajeta Guide

Chapters — 14. Templates & wildcards
  1. Introduction
  2. 0. Introduction
  3. 1. Installation
  4. 2. Kick the tires
  5. 3. Your first project
  6. 4. Running your application
  7. 5. Debugging
  8. 6. Keywords
  9. 7. Comments
  10. 8. Native types
  11. 9. Type kinds
  12. 10. Allocation
  13. 11. Ownership & borrowing
  14. 12. Control flow
  15. 13. Strings & formatting
  16. 14. Templates & wildcards
  17. 15. Lambdas & captures
  18. 16. Operator overloading
  19. 17. Inheritance
  20. 18. Annotations & synthesis
  21. 19. DI & aspects
  22. 20. Error handling
  23. 21. Reflection
  24. 22. Differentiation
  25. 23. Code coverage
  26. 23. Profiling
  27. 24. Notebooks
  28. 25. Writing XPU Kernels

14 — Templates & wildcards

Cajeta templates monomorphize. There is no erasure: each (class, type-argument) pair compiles to a distinct runtime type with its own field layout and vtable. Box<int32> stores a bare int32 — no boxing — and Box<String> is a separate compiled class. Tour demo: TemplatesDemo.

Class templates

public class Box<T> {
    public T value;
    public Box(T v) { this.value #= v; }
    public T get() { return this.value; }
    public void set(T v) { this.value #= v; }
}

A Box holds whatever it is handed, so it takes a plain T and stores it with #=, the container shape from chapter 11. set(v) lends and set(#v) transfers. Storing a T parameter with a plain = is rejected as CAJETA_ERROR_KEEP_NEEDS_SHARP_STORE, because a caller that passed #v would have the value freed when the call returns. A parameter that is only ever borrowed is spelled ^T and may be stored with =. The mark does nothing when T is instantiated with a primitive, and it never goes on the type-parameter declaration itself (class Box<^T> is an error).

Method templates

A single method can carry its own type parameters. The parameter list sits after the method name, not before the return type, and the method must be final (instance) or static — templated methods are excluded from the vtable, and the modifier makes that explicit. Each call-site type tuple monomorphizes separately. See MethodLevelTemplate.md.

public class Pick {
    public final U first<U>(U a, U b) { return a; }
    public static V second<V>(V a, V b) { return b; }
}

Numeric pseudo-bounds

Class bounds (<T extends Base>) can’t admit primitives, so three built-in pseudo-bounds do: Numeric (all integer and float types), Integral (integers only), Floating (float32/float64). Arithmetic on T lowers to the primitive op directly — no boxing, no vtable hop. Instantiating outside the bound (Bitset<float64>) fails at compile time with CAJETA_ERROR_TYPE_PARAMETER_BOUND. Tour demo: NumericTemplatesDemo; spec: numeric-bounds-spec.md.

public class Duo<T extends Numeric> {
    public T x;
    public T y;
    public Duo(T x, T y) { this.x = x; this.y = y; }
    public Duo<T> add(Duo<T> o) { return stack Duo<T>(this.x + o.x, this.y + o.y); }
}
public class Bitset<T extends Integral> {
    public T value;
    public Bitset(T initial) { this.value = initial; }
    public void set(int32 bit) {
        T mask = 1L << bit;
        this.value = this.value | mask;
    }
}
Duo<int32> a = stack Duo<int32>(1, 2);
Duo<float64> b = stack Duo<float64>(0.5, 1.5);
Bitset<int64> bits = heap Bitset<int64>(0L);
bits.set(3);

Duo<int32> and Duo<float64> are distinct compiled classes, one with int32 fields and one with float64 fields.

Wildcards

Box<Dog> is not a Box<Animal> — invariance is the default. Wildcards express variance at the use site, the standard PECS shape (Producer Extends, Consumer Super). Tour demo: WildcardsDemo; spec: TemplateWildcard.md.

public class Animal {
    public int32 tag() { return 1; }
}
public class Dog extends Animal {
    public int32 tag() { return 2; }
}
public class Kennel {
    public static int32 inspect(Box<? extends Animal> b) {
        return b.value.tag();
    }
}

Box<? extends Animal> accepts a Box of any Animal subtype. Reads project through the upper bound (b.value.tag() dispatches virtually); foreign writes are rejected with CAJETA_ERROR_PECS_WRITE_VIOLATION. Box<? super Dog> is the reverse: writes of Dog are accepted, reads don’t project. Box<?> is unbounded — any instantiation fits.

Box<Dog> bd = heap Box<Dog>(heap Dog());
int32 tag = Kennel.inspect(bd);
Box<? super Dog> sink = heap Box<Animal>(heap Animal());
sink.value = heap Dog();

Capture conversion

A wildcard hides T, but the compiler still tracks it per receiver: a value read from a wildcard receiver can be fed back to the same receiver, even though T is unnameable in source:

Box<? extends Animal> b = heap Box<Dog>(heap Dog());
b.set(b.get());

Next: 15 — Lambdas & captures.

Source: docs/guide/14-templates.md · 2 min · 400 words