You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Aether's actor model already declares message types — LLM.md notes this as the contrast with Erlang's duck-typed mailboxes:
Actor model is Erlang-ish but message types are declared, not duck-typed. receive + send, not ! / mailbox-matching.
Declared types catch shape errors at compile time, but they do not catch shared-aliasing-across-send. Today, nothing in the type system stops a sender from holding a reference to a heap-allocated field of a just-sent message while the receiver mutates it. With ref-counted strings (AetherString*), this is mostly papered over by reference counting; with pointer-bearing struct payloads (file handles, future capability tokens, byte buffers from fs.read_binary), it is a real race.
Nim's Isolated[T] (which is itself a descendant of Pony's reference capabilities) is the cleanest small-surface answer: a wrapper type that asserts at compile time "this value's transitive heap graph has exactly one owner; sending it transfers that ownership; the sender cannot use the value after send." Move-only at the language level. No runtime cost.
Why this fits Aether specifically
Actor-aligned. Aether is already type-checking messages; Isolated[T] adds an axis (linearity) without adding a new construct.
Capability-discipline-aligned. A Capability / GrantedFD distinct type wrapped in Isolated[…] is the natural shape for "a capability token that one actor passes to another, exactly once." Matches the design intent of the LD_PRELOAD grant list — capabilities should not be silently shareable.
Non-GC-friendly.Isolated[T] is not a runtime structure. The whole check is in the type checker / sigmatch. Lowers to plain C with no overhead.
Composes with the split-accessor / TLS-buffer pattern (LLM.md § "Split-accessor pattern for multi-return"): a *StringSeq that was just built from a TLS drain can be wrapped Isolated[*StringSeq] and sent, with the sender losing its local handle in the same compile-time step.
Sketch — surface
type Msg = struct {
body: *StringSeq,
op: int
}
actor Worker {
on msg: Isolated[Msg] => {
// msg.consume() unwraps once; subsequent access is a compile error
let m = msg.consume()
...
}
}
func send_work(w: actor Worker, body: *StringSeq, op: int) {
let m = Msg{ body: body, op: op }
// wrap. After this line, `m` and `body` cannot be referenced by name.
let iso = isolate(m)
send(w, iso)
}
Open design questions for docs/isolated.md:
Is the wrapper a built-in type (Isolated[T] known to the compiler) or a stdlib type with a compiler-recognised pragma?
How does isolate(...) prove the graph is unaliased? Likely the same dataflow Aether already does for refcount drop-vs-borrow.
Does receive auto-unwrap (clean) or hand the user the wrapper (explicit)?
Interaction with *StringSeq's structural sharing — seq_cons-shared tails are not isolated. Probably the answer is "isolation is shallow; deep-share types can't be wrapped" with a clear diagnostic.
What's required
docs/isolated.md design doc covering the four questions above.
Parser + type checker support for the Isolated[T] shape.
Dataflow check at isolate(...) site (no other live refs to the value).
consume() lowering (compile error on second consume; no runtime token).
tests/regression/actor_isolated_*.ae — pass cases, fail cases, and a "tries to use after send" case that must produce a specific diagnostic.
Why file this now, not later
Aether has at least two downstream-known motivating cases already documented:
Motivation
Aether's actor model already declares message types —
LLM.mdnotes this as the contrast with Erlang's duck-typed mailboxes:Declared types catch shape errors at compile time, but they do not catch shared-aliasing-across-send. Today, nothing in the type system stops a sender from holding a reference to a heap-allocated field of a just-sent message while the receiver mutates it. With ref-counted strings (
AetherString*), this is mostly papered over by reference counting; with pointer-bearing struct payloads (file handles, future capability tokens, byte buffers fromfs.read_binary), it is a real race.Nim's
Isolated[T](which is itself a descendant of Pony's reference capabilities) is the cleanest small-surface answer: a wrapper type that asserts at compile time "this value's transitive heap graph has exactly one owner; sending it transfers that ownership; the sender cannot use the value aftersend." Move-only at the language level. No runtime cost.Why this fits Aether specifically
Isolated[T]adds an axis (linearity) without adding a new construct.Capability/GrantedFDdistinct type wrapped inIsolated[…]is the natural shape for "a capability token that one actor passes to another, exactly once." Matches the design intent of the LD_PRELOAD grant list — capabilities should not be silently shareable.Isolated[T]is not a runtime structure. The whole check is in the type checker / sigmatch. Lowers to plain C with no overhead.LLM.md§ "Split-accessor pattern for multi-return"): a*StringSeqthat was just built from a TLS drain can be wrappedIsolated[*StringSeq]and sent, with the sender losing its local handle in the same compile-time step.Sketch — surface
Open design questions for
docs/isolated.md:Isolated[T]known to the compiler) or a stdlib type with a compiler-recognised pragma?isolate(...)prove the graph is unaliased? Likely the same dataflow Aether already does for refcount drop-vs-borrow.receiveauto-unwrap (clean) or hand the user the wrapper (explicit)?*StringSeq's structural sharing —seq_cons-shared tails are not isolated. Probably the answer is "isolation is shallow; deep-share types can't be wrapped" with a clear diagnostic.What's required
docs/isolated.mddesign doc covering the four questions above.Isolated[T]shape.isolate(...)site (no other live refs to the value).consume()lowering (compile error on second consume; no runtime token).tests/regression/actor_isolated_*.ae— pass cases, fail cases, and a "tries to use after send" case that must produce a specific diagnostic.Why file this now, not later
Aether has at least two downstream-known motivating cases already documented:
LLM.md§ sandbox) wants single-use, non-shareable tokens;Isolated[Capability]is the type-level expression of that.Acceptance criteria for this issue
This issue is for agreeing the design, not shipping the feature. Closing it produces:
docs/isolated.mdanswering the four open questions.References
LLM.md§ "Actor model is Erlang-ish" — current message-type storyLLM.md§ "Ownership ofptr-typed returns" — the borrowed-vs-ref-counted distinctionIsolated[T]extendslib/std/isolation.nim— reference implementation in a non-GC-by-default language