Runtime Functions
August 2, 2026 ยท View on GitHub
Runtime functions implement standard-library components declared with
#extern. They are an implementation
boundary, not the default way to add behavior: prefer a Neva graph when the
behavior can be expressed clearly with existing public components.
internal/runtime/messages/ imports only the Go standard library and owns
immutable language values plus pure value operations. internal/runtime/ may
also import internal/runtime/messages for ports, ordering, tracing, and
program execution. Runtime functions under internal/runtime/funcs/ may import
both packages: use messages for pure value work and runtime for transport.
Import messages directly. Prefer the change that minimizes total system
complexity, even when it requires more work now; do not add transitional
adapters, aliases, or duplicate paths solely to postpone that work.
Before Adding a Runtime Function
- Inspect equivalent components in
std/,internal/runtime/funcs/, and their tests. - State why a Neva composition is insufficient: required primitive semantics, unavailable state, or a measured hot-path requirement are valid reasons.
- Keep the public component signature in
std/, its#externname, and the entry ininternal/runtime/funcs/registry.gosynchronized.
Execution Contract
A creator resolves its ports once and returns a function that processes
messages until its context is cancelled or a port operation cannot continue.
Receive and Send return false when the operation stops because the
context is done; the runtime function must then return rather than continue
with a zero message.
Every message derived from received input must pass the received OrderedMsg
values to Send as causes. This preserves runtime ordering and dataflow
tracing.
Compiler Invariants
The entire runtime may rely on the static type guarantees of the Neva
compiler. A value that contradicts its declared type is a runtime invariant
violation and must panic. Do not convert a compiler or runtime implementation
defect into a public Neva error value.
Use a public error output only for failures possible in a well-typed program, such as a missing dictionary key or an out-of-bounds list index.
Typed Containers
The public Neva values remain list<T> and dict<T>, but scalar containers
can retain unboxed Go storage such as []int64 or map[string]string.
Preserve that representation on scalar hot paths. Convert each element to an
individual runtime message only at a boundary that genuinely requires one, such as
conversion to a stream. Existing boxed containers may retain their backing
storage; converting typed scalar storage deliberately allocates a new message
slice or map.
Equality is a pure value operation. It compares equivalent typed and untyped container storage; runtime functions must not reimplement it or depend on a particular storage representation.
Equality must preserve the storage representation. Compare two containers with
the same typed scalar representation directly. Compare a typed container and
an untyped container incrementally, without materializing an entire typed
container as []Msg or map[string]Msg. The same rule applies recursively:
nested containers must not cause whole-container boxing merely to perform
equality or matching.
Container inspection and transformation are value operations as well. Keep representation interfaces limited to access to their storage; do not add semantic operations as methods on those interfaces.
The same ownership rule applies beyond containers. messages owns pure scalar
arithmetic, comparison, conversion, parsing, formatting, string transformation,
regular-expression matching, and struct traversal. A runtime function may map
a returned value error to a public Neva error, but must not reimplement the
underlying computation. Constructors such as NewStructMsg and NewUnionMsg
remain the value-layer primitives; ports, stream framing, external I/O, state,
and tracing remain outside messages.
Name a public operation specific to one value type with the type first, then
the action and any necessary detail. Constructors retain the usual New<Type>
form. Generic operations over all messages are exempt from this convention.
Streams
stream<T> is a concrete union value protocol: Open, zero or more Data T,
then Close. messages owns construction, classification, and payload
decoding of those immutable union values. Use the stream helpers in
messages; do not duplicate tag strings or union assertions in runtime
functions.
Runtime functions own stream transport: port I/O, causes, cancellation,
waiting for Open, draining through Close, and coordination state machines.
When collecting a stream or array into a list or dict, use NewListMsg or
NewDictMsg. They preserve typed scalar storage when all collected values
have the same scalar representation; mixed, nested, and empty collections
remain untyped. Use NewUntypedListMsg or NewUntypedDictMsg only when boxed
storage is deliberately required.
Concurrent Inputs
Inputs that belong to one logical operation must be received concurrently.
Use receive2, receive3, or receive4, or add a narrowly scoped equivalent
when the arity requires it. Receiving independent ports sequentially can block
the graph even when each sender is correct.
Sequential reception is permitted only when it is the component's deliberate public protocol. Document that protocol near the code and cover it with a test. Do not use sequential receives merely to make local control flow simpler.
Stateful Functions
State is local to one runtime-function instance unless the component contract explicitly requires a shared runtime service. Define the ownership, lifecycle, and contention boundary before adding locks or shared tables. A lock around a single shared instance can become a program-wide bottleneck even when the type permits many instances.
Prefer an explicit public runtime API or a dependency passed through an existing runtime boundary over package-level mutable singletons.
Tests and Comments
Add focused unit tests for every runtime behavior changed, including normal, termination, and meaningful corner cases. Add e2e coverage for the exposed Neva component when a graph-level contract is affected. Benchmarks measure a performance claim; they do not replace behavior tests.
New Go functions and types need doc comments. For non-obvious concurrency, ordering, or state blocks, explain the invariant and why the chosen protocol is safe.
Resolved Type Descriptors
std/reflect.Type is the portable runtime representation of a
compiler-resolved structural type. It is an ordinary immutable Neva message,
not a new runtime message kind and not metadata attached to every value.
The descriptor is a flat list<reflect.TypeNode> rooted at index zero. Every
composite edge stores an integer index into the same list. This represents
ordinary nesting, shared sub-shapes, and recursive back-edges with one finite
format. The representation contains runtime-relevant structural shape only;
source aliases, constraints, and generic parameters are absent.
internal/runtime/messages owns canonical conversion between that wire value
and its native Go representation. Consumers may compile a private execution
plan from it, but must not introduce a process-global type registry or attach
the descriptor to ordinary messages. The initial descriptor substrate does not
provide public TypeOf or general reflection.