Skip to Content

Events

An event declares a broadcast channel: one emit and every subscriber receives the payload in turn. An event can live at module top level or on a type, where it becomes an instance event.

Declaring and Emitting

event Ticked(n: int); event Ping();

The payload is a positional parameter list; emit channel(args); fires the event. Subscribe with ~>, unsubscribe with !~> (safe even when not subscribed):

event Ticked(n: int); func OnTick(n: int) { print("tick", n); } func OnTick2(n: int) { print("tick2", n); } Ticked ~> OnTick; Ticked ~> OnTick2; emit Ticked(5); Ticked !~> OnTick2; emit Ticked(6);
tick 5 tick2 5 tick 6

After unsubscribing, one subscriber remains. <~ is the same subscription written the other way round (OnPing <~ Ping; equals Ping ~> OnPing), matching the reading order “the consumer subscribes”.

Dispatch Order and Deduplication

Dispatch follows subscription order (first subscribed, first dispatched) and completes synchronously: handlers run one by one inside emit’s call stack, regardless of declaration order:

event Ticked(n: int); Ticked ~> (n: int) => print("h2", n); Ticked ~> (n: int) => print("h1", n); Ticked ~> (n: int) => print("h3", n); emit Ticked(1);
h2 1 h1 1 h3 1

Subscribing the same handler to the same event more than once deduplicates: it takes one slot and dispatches once, and a single !~> removes it completely.

Reentrancy

Emitting again while a handler runs (the same event or another) is legal: the inner emit dispatches recursively right away, nesting like the call stack, and the outer level continues only after the inner one has fully finished. Unsubscribing yourself mid-dispatch does not affect the current round; from the next round on, you stop receiving:

event A(n: int); A ~> (n: int) => { print("enter", n); if n < 2 { emit A(n + 1); } print("exit", n); }; emit A(1);
enter 1 enter 2 exit 2 exit 1

Handler Shapes

A handler can be a named function or a lambda. The return type is unconstrained; declaring -> int is legal and the return value is ignored.

Note

A subscribed handler must be a plain function: no fail, no uses, not async.

Instance Events

An event declared on a class is an instance event; the dispatch table is isolated per instance:

class Counter { public event Changed(from: int, to: int); public var value: int = 0; public func Set(v: int) { val old = value; value = v; emit Changed(old, v); } } func OnCh(from: int, to: int) { print("chg", from, "->", to); } val g = new Counter(); g.Changed ~> OnCh; g.Set(1); g.Changed !~> OnCh; g.Set(2);
chg 0 -> 1

Unsubscribing from an instance event, g.Changed !~> h, works the same as at top level. Whether a subscription dies automatically after the instance drops its last reference is up to the host GC — the language makes no promise, and the handle’s IsCancelled does not become True because of it.

Event Props

An event prop (event prop) goes a step further: writing is broadcasting. The payload is fixed at (old, new) and a change check is built in — the second = 5 produces no broadcast:

class Gauge { public event prop Level: int; } val g = new Gauge(); g.Level ~> (oldV: int, newV: int) => { print($"level {oldV} -> {newV}"); }; g.Level = 5; g.Level = 5; g.Level = 8;
level 0 -> 5 level 5 -> 8

The change check happens after the before segment rewrites the value. A same-value write skips the entire write chain — storage, the after segment, and the automatic emit are all skipped; only finally always runs, with or without subscribers (pipeline segment syntax: Event Pipelines).

Generic Events

event Changed<T>(v: T); can only be declared at top level; each instantiation is a channel. Type arguments can be inferred three ways: explicit instantiation (Changed<int> ~> onInt), from the handler’s parameters (Changed ~> (v: int) => ...), or from the emit arguments (emit Changed(43);).

Different instantiations are different channels: a subscriber of Changed<int> never receives emit Changed("s"). The two channels are fully separate, and nothing errors.

Notes

  • Events are also an operand of await: await <event source> waits once for the next firing and returns the payload. See Awaiting Events.
  • Events carry before / after / finally pipeline segments and the eight-step write sequence. See Event Pipelines.
  • For the subscription handle a subscription produces, see Subscription Handles and Event Values.
Last updated on October 11, 2026