- language: `spawn Cls { fields }` expression (ctor semantics — fields
MOVE; result is the address); `actor M` parametric field type
(contextual like multi/map — actor stays a legal identifier); `send`
is a builtin free-fn name, not a keyword (shadowing rule applies)
- typing: M inferred from Cls's receive(msg: M); WO-E221 when receive
is missing, mis-armed, or M is not a class/record/union; send checks
addr is `actor M` and the message IS an M (silent when underivable);
ctor half of spawn delegates to the Ctor arm (completeness, ?T, E207)
- ownership: send's message TRANSFERS (sender's later use = WO-E301,
corpus-pinned); spawn's fields move via the ctor machinery; an
address is Copy
- emit: spawn lowers to ctor + LOADK receive's method index + BUILTIN
68; send is BUILTIN 69 with the message excluded from fresh-arg drops
(the runtime owns it now)
- runtime: wo_actor (moved-in instance, receive idx, growable FIFO
mailbox, one delivery fiber at a time); delivery reuses the fiber
context across messages and re-queues per message (fairness — an
actor never monopolizes); the runtime drops each message after its
receive returns; actor state/queued/in-flight messages are GC roots;
teardown drops everything (main-return reap included); loader knows
the two arities
- corpus: run/actor-echo (typed spawn/send, one-at-a-time delivery
interleaved with main by budget — output exact, ASan-clean),
compile-fail/spawn-no-receive (WO-E221), send-after-move (WO-E301)
- battery green: oop-e2e 92/0, woc-test, wovm-test, log-watcher 7/0,
employee 8/0, web-app 21/0, deps-accept 8/0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
34 lines
939 B
Text
34 lines
939 B
Text
-- the arc: spawn/send/actor M — an actor is a class with receive(msg: M);
|
|
-- messages MOVE; delivery is one message at a time; main's return reaps
|
|
-- idle actors clean (ASan gate covers this fixture too).
|
|
|
|
class Note {
|
|
text: Text
|
|
}
|
|
|
|
class Logger {
|
|
prefix: Text
|
|
seen: Int
|
|
fn receive(msg: Note) {
|
|
self.seen = self.seen + 1;
|
|
print("${self.prefix}${msg.text} #${self.seen}");
|
|
}
|
|
}
|
|
|
|
fn main() -> Int {
|
|
let a: actor Note = spawn Logger { prefix: "log: ", seen: 0 };
|
|
send(a, Note { text: "first" });
|
|
send(a, Note { text: "second" });
|
|
let n = Note { text: "third" };
|
|
send(a, n);
|
|
-- the scheduler drains actor messages when main yields or ends; a
|
|
-- backward-jump loop hands turns over under the default budget? No:
|
|
-- default budget is 4000 — force turns with a tiny spin so delivery
|
|
-- interleaves before main returns
|
|
let i = 0;
|
|
while i < 20000 {
|
|
i = i + 1;
|
|
}
|
|
print("main done");
|
|
return 0;
|
|
}
|