Records the user-chosen direction for telling the compiler a proc's wire
format, applicable across the whole pragma family ({.ffi.}, {.ffiHost.}, …):
- per-proc pragma argument: {.ffi: raw.} / {.ffi: cbor.} (A1), default native;
- library-wide override declareLibrary(defaultAbiFormat = raw) (C2);
- the global -d:ffiMode compile flag is discarded.
'raw' (not 'native') names the zero-serialization C-POD format. Design only;
captures open questions (symbol emission, resolution order, codegen impact) for
further discussion before implementation.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.2 KiB
nim-ffi — future work
Ideas for making nim-ffi a best-in-class FFI solution for exposing Nim to any platform. Captured from design discussion; not yet scheduled unless linked to a branch/PR.
Foundation: the dual-proc design
A {.ffi.} / {.ffiCtor.} / {.ffiDtor.} proc compiles into two procs that
share the source name:
- a normal, fully-typed Nim proc (the user's body) — callable in-process with zero serialization, and unit-testable without any FFI; and
- an
{.exportc, cdecl, dynlib.}wrapper with the(ctx, cb, ud, …)ABI that foreign callers bind.
Nim disambiguates by overload resolution (see ffi/internal/ffi_macro.nim, the
note at the cExportProcName definition). Most items below build on this: the
same source can serve an in-process Nim caller and a foreign caller over the C
ABI, choosing the transport per call site.
Roadmap (priority order)
1. Typed bidirectional calls — host-provided functions the Nim side can await ⬅ in progress
Today data flows lib → host as events (raw/CBOR). The inverse is missing: a Nim
{.ffi.} proc calling back into the host language for typed data and
awaiting the result — the "a lower layer needs to read from a higher one" case
(logos-delivery issue #3865). A {.ffiHost.}-style annotation turns a
bodyless typed Nim proc into a call that marshals to a host-registered function
pointer and resolves a chronos Future when the host calls back. Reuses the
event machinery (registry + ThreadSignalPtr bridging into chronos). This is
the feature that changes what people can build with nim-ffi.
2. Richer error model than string
Result[T, string] crosses today. Allow Result[T, E] where E is a typed
{.ffi.} struct, so every language surfaces structured errors (codes, fields)
instead of parsing text. Small change to the macro's return handling.
3. Streaming / multi-shot results
A proc that yields many values (an AsyncStream) mapping to host-native
iterators: Kotlin Flow, Swift AsyncSequence, Rust Stream, JS async
iterators. Turns nim-ffi from RPC into a reactive core.
4. ABI self-descriptor symbol
Export <lib>_abi_descriptor() returning the schema (CBOR/JSON) so a host can
validate compatibility at load time. Addresses the deferred CBOR wire-versioning
concern.
Cross-cutting decisions
- ABI format selection (design-abi-format.md) — per-proc
pragma arg
{.ffi: raw.}/{.ffi: cbor.}(default native/raw), with a library-widedeclareLibrary(defaultAbiFormat = …)override. Global compile flag discarded. Applies to{.ffiHost.}too. Direction decided, design only.
Adjacent / parallel tracks (already discussed elsewhere)
- seq/Option + multi-struct param marshaling parity for the Swift (#59) and
Kotlin (#60) generators —
go.nimis the reference (it already does this). - Typed events on Swift/Kotlin — the JNI-thread-attach-into-JVM case for Kotlin is the hard part.
- Async idiom mapping —
Future[T]→ Promise /async/await/suspend/impl Future, so callersawaitinstead of blocking on a semaphore. - WASM Component Model (WIT) emitter — emit a
.witso any host consumes the interface without bespoke glue.