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§
Provided Methods§
Sourcefn note_call_budget(&mut self, _budget_cost: u64) -> Result<(), String>
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.
Sourcefn enter_request_scope(&mut self) -> u64
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.
Sourcefn exit_request_scope(&mut self, _scope_id: u64)
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.
Sourcefn spawn_for_worker(&self) -> Option<Box<dyn EffectHandler + Send>>
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".