Skip to main content

EffectHandler

Trait EffectHandler 

Source
pub trait EffectHandler {
    // Required method
    fn dispatch(
        &mut self,
        kind: &str,
        op: &str,
        args: Vec<Value>,
    ) -> Result<Value, String>;

    // Provided methods
    fn note_call_budget(&mut self, _budget_cost: u64) -> Result<(), String> { ... }
    fn enter_request_scope(&mut self) -> u64 { ... }
    fn exit_request_scope(&mut self, _scope_id: u64) { ... }
    fn spawn_for_worker(&self) -> Option<Box<dyn EffectHandler + Send>> { ... }
}
Expand description

Host-side effect dispatch. Implementors decide what kind/op mean and how arguments map to side effects.

Required Methods§

Source

fn dispatch( &mut self, kind: &str, op: &str, args: Vec<Value>, ) -> Result<Value, String>

Provided Methods§

Source

fn note_call_budget(&mut self, _budget_cost: u64) -> Result<(), String>

Hook called by the VM at every function call so handlers can enforce per-call budget consumption (#225). The argument is the sum of [budget(N)] declared on the callee’s signature; the handler returns Err to refuse the call (the VM converts to VmError::Effect). Default impl is a no-op so legacy handlers and pure-only runs are unaffected.

Source

fn enter_request_scope(&mut self) -> u64

Enter a per-request allocation scope (#463 scaffolding). Called by the runtime layer (e.g. net.serve_fn’s request loop) immediately before invoking the user handler closure for one request. Implementations push a fresh arena onto their internal stack and return its identifier; the matching exit_request_scope call drops it.

Default impl is a no-op — handlers without arena support return a sentinel scope id which they ignore on exit. DefaultHandler in lex-runtime provides the real implementation.

Today the VM does NOT route any Value allocations through the returned arena — see the scaffolding notes in crates/lex-runtime/src/arena.rs. The hook exists so the follow-on slice that adds Value-rep arena routing has a stable trait surface to extend.

Source

fn exit_request_scope(&mut self, _scope_id: u64)

Exit a per-request allocation scope opened by enter_request_scope. Implementations drop the arena associated with scope_id. Calling exit with a scope_id that wasn’t returned by a prior enter is implementation- defined behavior — DefaultHandler treats it as a no-op so mismatched pairs don’t panic.

Source

fn spawn_for_worker(&self) -> Option<Box<dyn EffectHandler + Send>>

list.par_map worker-handler factory (#305 slice 2).

Each parallel worker thread runs its own Vm and therefore needs its own effect handler. The parent handler may opt in to per-worker dispatch by returning Some(handler) here; returning None (the default) keeps slice-1 behavior: the worker runs DenyAllEffects and any effect call inside the closure fails with VmError::Effect.

The returned handler must be Send so the worker can take ownership across a thread boundary. Shared state (budget pool, chat registry, etc.) is wired up by the implementer. Per-worker independence (MCP client cache, output sink) is intentional — the alternative is mutex-serialization of the whole effect dispatch, which would defeat the parallelism.

Dyn Compatibility§

This trait is dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety".

Implementors§