module
stdlib/core/module.hk: dynamic Hanki module loading.
Core-tier substrate: module loading is welded to the runtime, and, like actor, it retains its own @intrinsics in place of delegating to sys (the sys seam is for opt-in OS capabilities; runtime module loading is substrate and none of those).
module.load!(path) loads a Hanki module and returns a Module<T> handle whose methods dispatch through a per-handle vtable populated at load time. T must be a trait declared in the host; the loaded module's exported def / def! items are matched against T's declared methods by name + structural signature.
T comes from the binding's annotation, and there is no other way to supply it: the language has no call-site type argument.
m: Module<Greeter> = module.load!("greeter.hk")
A loaded module may instead expose an actor: where T's ! methods match a loaded actor's on handlers (by name + signature), load! spawns that actor and the Module<T> handle drives it: m.method!(args) is a blocking send, and the actor is live and stateful until unload! stops it (HANKI.md §14). Bytecode tier only for now.
Both tiers load a .hk source file and never a pre-built artifact: the bytecode tier (hanki run) compiles it to bytecode and binds its exports, and the AOT tier (hanki build) does the same by linking an embedded interpreter, and only into a load!- or reload!-using binary. On the AOT-embedded loader, loaded actors and a loaded method's actor operations (spawn/send/await) are not yet supported; function exports, their OS-seam effects (io/fs/net), and their throws all work there.
Module
type Module<T>
end
Module<T> is the handle type, opaque to user code: the runtime owns its representation. Construct via load!(path); release via unload!(m).
ModuleLoadError
type ModuleLoadError
VersionMismatch
IncompatibleInterface(string, string)
DlopenFailed(string)
ManifestMissing
ManifestMalformed(string)
StateMigrationRequired(string)
ManifestVersionUnsupported(string)
InvalidHandle(string)
ModeMismatch(string)
end
Reasons load! / reload! can fail. The runtime constructs these by tag, and variant order therefore matters and is mechanically checked: both runtimes build from the shared tag table, and a compiler currency test asserts that table against this declaration order.
StateMigrationRequired means what it says: add a migrate hook, or fix it), recompile, retry. The failures no hook can fix are their own variants: InvalidHandle (the reload target is dead or unloaded) and ModeMismatch (the new source binds an actor where the handle has functions, or vice versa).
ModuleHandleInvalid
type ModuleHandleInvalid
Released
end
Raised when a method is dispatched through a Module<T> handle that unload! has already released. Distinct from ModuleLoadError: a a use-after-release being no load failure, and a program catches the two separately. Every Module<T> method call therefore takes [throws ModuleHandleInvalid]: the throw is a property of dispatching through a handle that may be gone and no property of the method (a pure def method call charges it). Both tiers raise the identical value, so use-after-unload has one catchable failure form across bytecode and AOT.
load!
def load!<T>(path: string) -> Module<T> [io, throws ModuleLoadError]
Open a Hanki module from path and bind its exports against the host trait T. The vtable is built once at load time; subsequent m.method(args) calls are direct dispatches through it.
Throws ModuleLoadError on any failure; wrap the call in try ... catch e: ModuleLoadError ... end to handle. @no-doctest: loads a separate module file; not expressible as a self-contained doctest
unload!
def unload!<T>(m: Module<T>) -> () [io]
Release a previously-loaded module. A method dispatched through the handle after unload! returns throws ModuleHandleInvalid (catchable, identical on both tiers) in place of dispatching into released code, and use-after-release fails safely, never as undefined behaviour. For an actor-mode handle unload! also stops the spawned actor; a handle dropped without unload! leaves its actor running until program shutdown. @no-doctest: releases a live loaded module; nothing to assert on in a doctest
reload!
def reload!<T>(m: Module<T>, path: string) -> Module<T> [io, throws ModuleLoadError]
Reload a previously-loaded module from path (HANKI.md §14): recompile the source, re-bind it to T, and swap it over the handle. The same Module<T> handle is returned, re-pointed at the new code. The new module must bind in the handle's mode (actor or functions); a mode change throws ModeMismatch, and a dead or already-unloaded handle throws InvalidHandle.
For an actor-mode handle the swap lands at the live actor's next safe point: the in-flight handler finishes under the old code, every subsequent message runs the new; the actor and its mailbox survive. When the new code's actor state layout differs from the running one, the actor's migrate(old: T) hook rebuilds the state from the old; without a matching hook the reload throws StateMigrationRequired and the actor retains its old code (reload is atomic). Actor-mode reload is bytecode tier only, like actor-mode load!.
For a function-mode handle the swap is just re-pointing the vtable at the recompiled code, with no actor to quiesce and no state to migrate. It works on both tiers.
Throws ModuleLoadError on any failure, leaving the running code untouched. @no-doctest: reloads a separate module file; not expressible as a self-contained doctest