An actor class runs on its own thread with private state. You hold a typed handle actor[T]; calls cross the thread boundary through async (dispatch) and await (collect). |
|
Mark a class with actor. Every new spawns a fresh worker thread, and the variable receives an actor[T] handle, not the instance itself. |
actor class Counter() {
int n = 0;
func bump() int {
n += 1;
n;
};
};
actor[Counter] c = new Counter();
|
Call synchronously with await receiver.method(args) — async is implied. Reach for a bare async only when you want the future[T] itself, to overlap work, and await it later. |
int n = await c.bump(); # 1 — synchronous call
future[int] f = async c.bump(); # start without waiting
int m = await f; # 2 — collect later
|
Each actor owns its data — two actors mean two threads, two private ns, no shared memory, no locks. |
actor[Counter] a = new Counter();
actor[Counter] b = new Counter();
await a.bump(); # 1
await b.bump(); # 1 — independent
await a.bump(); # 2
|
Because async returns immediately, two calls on two actors run in parallel. Wait time is the slower one, not the sum. |
future[int] fa = async a.bump(); # both dispatched
future[int] fb = async b.bump(); # before either blocks
int x = await fa;
int y = await fb;
|
Arguments and return values are deep-copied across the boundary (primitives, arrays, tuples, and data class values). Other actor[T] values cross by reference. Plain class instances, function values, and pointers can't — make them a data class or wrap them in their own actor. |
actor class Bag() {
array[int] held = new array[int]{};
func put(array[int] xs) void { held = xs; };
};
actor[Bag] b = new Bag();
array[int] xs = new array[int]{1, 2, 3};
await b.put(xs);
xs[0] = 99; # local mutation —
# the actor's copy is unaffected
|