FFI & Dynamic
Embed Nomi in a host program
Section titled “Embed Nomi in a host program”One goal is to let an existing program embed Nomi as a language for the parts that benefit from it: configuration, policy, rules, workflows, scripting, or user-defined behavior.
Today that host is Go. In that shape, the Go program owns the process, sets up the runtime, provides any host capabilities Nomi is allowed to use, and calls into Nomi code. Nomi code can stay small and typed, while the host keeps control over IO, storage, networking, cancellation, and deployment.
Packages backed by host libraries
Section titled “Packages backed by host libraries”Another goal is to let a Nomi package wrap a host library with low ceremony. A database package, for example, might use a Go SQL library underneath while exposing a Nomi API made of ordinary modules, functions, resources, and typed values.
That is the shape Nomi wants users to see:
resource conn = try Sqlite.open("app.db") rows = try Sqlite.query_all(conn, "select name from users")
The package may have foreign handles or host-backed functions internally, but callers should not have to think about those details. The boundary should disappear behind a small Nomi API whenever the types are known.
Direct but safe interop
Section titled “Direct but safe interop”There may also be a more direct way to interface with Go APIs safely. That design is not settled. The direction would be explicit and typed, not “anything can call anything”: Nomi should know what values cross the boundary, which operations can fail, and which host capabilities are being exposed.
The open design question is how much ceremony is right. Too much ceremony makes host-backed packages tedious to write; too little makes the boundary unclear and unsafe.
Dynamic values
Section titled “Dynamic values”Some boundary values really do not have a known shape: decoded payloads,
plugin messages, database rows, or data from another runtime. Dynamic is
the escape hatch for that case. Code can carry the value through the type
system, then decode it explicitly when it knows what shape it expects.
When a format is known, prefer a typed representation. For JSON, std/json
decodes into a Json enum so ordinary case matching can handle the shape.
Dynamic is useful at the edge, but it should not be the default way to model
data inside Nomi.
That’s the tour. You’ve seen Nomi’s core syntax (Bindings & Expressions, Functions, Pipes), its data story (Scalars, Collections, Structs/Enums, Pattern Matching), its abstraction layer (Interfaces, Modules), its distinctive features (App Fields, Resources, Concurrency, Dates & Times, Typed Literals), and how it talks to host programs. The Standard Library reference is generated from the stdlib’s own docs.