Actors & Message Loops

Autonomous concurrent entities with isolated private state, typed mailboxes, and sequential message loops. Zero locks, zero deadlocks, compiled to strict ISO C11.

Actor Model Share Nothing Thread Isolation

🎭 What is an Actor?

In traditional multi-threaded programming, threads communicate by mutating shared memory under mutex locks. This design leads directly to race conditions, deadlocks, and unpredictable timing bugs.

Vela adopts the Actor Model: an actor is an autonomous execution unit that combines:

The Share-Nothing Invariant: Inside an actor handler, state mutation requires no locks or atomic operations because no other thread can ever access that state. Communication happens strictly through message passing!

📝 Syntax and Declaration

An actor is declared with the actor keyword, followed by its state parameters and message handlers:

actor Counter(count: int)

    on Increment(step: int)
        count += step
    fi

    on Get(reply: Chan[int])
        reply.dispatch(count)
    fi
fi

Spawning and Dispatching Messages

Actors are spawned with the spawn expression, returning an Actor[T] handle:

task main()
    fixed c = spawn Counter(0)

    ; Dispatch messages asynchronously (both method and functional syntax supported)
    c.dispatch(Counter.Increment(5))
    dispatch(c, Counter.Increment(10))

    ; Request-reply pattern via channels
    fixed reply = chan_new<int>(1)
    c.dispatch(Counter.Get(reply))
    fixed result = reply.wait_and_receive()
    show("Counter is: ", result)  ; 15

    ; Graceful shutdown
    c.close()
fi

⚖️ Actors vs. Events: What is the Difference?

Because both Actors and Events respond to occurrences, developers often wonder how they differ and when to use each:

Dimension Actors (actor) Events (event)
Execution Context Runs on its own concurrent thread/fiber loop. Runs synchronously within the caller's thread.
State Ownership Owns encapsulated private state (share nothing). Observes external state (fields, tasks, lifecycle).
Invocation Trigger Dequeuing a message from its mailbox channel. Field mutation (on .hp), task entry/exit, or lifecycle.
Concurrency Asynchronous, decoupled, non-blocking message send. Synchronous immediate hook with alters(...) verification.
Primary Purpose Safe concurrent state management and worker pipelines. Reactive UI triggers, invariant validation, and auditing.

The Powerful Synergy: Bridging Events to Actors

Actors and events work best together. An event hook can detect a critical state transition and immediately dispatch an alert message into an Actor's mailbox:

shape Thermometer
    temperature: int
    monitor: Actor[Monitor]

    event on_temp on .temperature auto
        when .temperature > 100
            .monitor.dispatch(Monitor.OverheatAlert(.temperature))
        fi
    fi
fi

The reactive trigger is immediate and verified at compile time, while the heavy processing is handled asynchronously by the actor without blocking the caller!

⚙️ Under the Hood: ISO C11 Codegen & M:N Scheduler

Vela compiles actors directly to efficient, standard ISO C11 multiplexed on an M:N green thread scheduler pool:

🛡️ State Safety Guarantees

  1. Zero Data Races on Actor State: External code cannot read c.count; any attempt triggers a compilation error: actor state is private.
  2. No Mutex Lock Contention: Handlers execute sequentially within the actor's thread, meaning handlers can mutate state freely without locking.
  3. Linear Ownership Transfer: Sending messages carrying own T relinquishes sender ownership, guaranteeing single-thread ownership across concurrency boundaries.