Skip to content

FFI & Dynamic

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.

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.

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.

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.