Component Constraints

Component constraint type

The *component[...] type accepts any component that implements specific @dispatch methods:

tape
fn render(widget: *component[@dispatch fn paint(ctx: *u8, x: i32, y: i32, w: i32, h: i32)]) {
    widget.paint(ctx, 0, 0, 100, 100);
}

Any component with a matching paint dispatch satisfies this constraint — no explicit implements declaration needed.

The is operator

Use is to check at runtime whether a component pointer satisfies a constraint:

tape
if (widget is *component[@dispatch fn paint(ctx: *u8, x: i32, y: i32, w: i32, h: i32)]) {
    widget.paint(ctx, x, y, w, h);
}

The compiler generates vtable slot checks — it verifies that each required dispatch slot is non-null in the component’s vtable.

Type aliases for constraints

The standard library defines type aliases for common constraints:

tape
type Drawable = *component[@tape {drawable();}];
type Layoutable = *component[@tape {layoutable();}];
type Interactive = *component[@tape {interactive();}];

These use @tape {} blocks inside the constraint to invoke the same comptime helpers that components use, ensuring the dispatch signatures always match.

Collections of constrained components

tape
fn render_all(widgets: []Drawable) {
    for (w in widgets) {
        w.paint(ctx, 0, 0, 100, 100);
    }
}

Multiple dispatches

A constraint can require multiple dispatch methods:

tape
type Widget = *component[
    @dispatch fn paint(ctx: *u8, x: i32, y: i32, w: i32, h: i32);
    @dispatch fn handle_pointer(ev: *ui.PointerEvent);
];

The is check ANDs all dispatch slot checks — all must be non-null.

Required vs optional dispatch

Within a component declaration, dispatches can be marked required:

tape
component Base {
    @dispatch(required) fn paint(ctx: *u8, x: i32, y: i32, w: i32, h: i32);
    @dispatch fn layout(ctx: *u8, x: i64, y: i64, w: i64, h: i64);
}

A @dispatch(required) must have a corresponding method implementation — the compiler errors if the method is missing.

How dispatch works

Each component gets a vtable — a table of function pointers indexed by global dispatch ID. The compiler:

  1. Assigns a global ID to each unique dispatch name + signature
  2. Generates a vtable per component with slots for its dispatches
  3. Emits indirect calls through the vtable for dispatch method calls
  4. For is checks, loads the vtable and verifies the required slots are non-null

Structural, not nominal

Constraints are structural — they match based on dispatch signatures, not names or explicit implementations. Two components with the same paint signature both satisfy Drawable, even if they were written independently.

Last modified: