internals · 3 of 6 · callbacks and exceptions
Every native callback crosses an engine C frame before it reaches Rust. The trampoline does five things, in order:
FunctionCallbackInfo pointer layout rusty_v8 expectsJSC records the pending exception and its context in IsoState on every call that can throw. QuickJS turns JS_TAG_EXCEPTION into JS_GetException. TryCatch, MaybeLocal, and the message functions read that stored state; they never ask the engine directly.
The implicit V8 machinery lives in one struct behind the opaque v8::Isolate pointer:
| backend | IsoState owns |
| JSC | context group, global contexts, entered-context stack, protected locals, pending exception, weak records, GC callbacks |
| QuickJS | runtime, contexts, handle arena, persistent cells, module tables, snapshot state, promise hooks, interrupt state, memory counters |
One ordering rule: QuickJS sizes each context’s class table at JS_NewContext, so external-object and named-handler classes register before the first context exists.
Neither engine has V8′s template concept. The template description (properties, accessors, callbacks, internal-field count) lives in a native adapter record, and instantiation reads it to build the real engine object. Internal fields go in backend-owned records tied to the engine object; they hold raw native pointers and stay invisible to JavaScript enumeration. Function templates store the Rust callback and its data, then install the trampoline above when materialized.
Next: Modules: identity across compile, instantiate, evaluate