nim-ffi/docs/future-work.md
Ivan FB 50b56c0cad
docs: ABI format selection decision (raw vs cbor)
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>
2026-06-13 23:01:16 +02:00

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:

  1. a normal, fully-typed Nim proc (the user's body) — callable in-process with zero serialization, and unit-testable without any FFI; and
  2. 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-wide declareLibrary(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.nim is 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 mappingFuture[T] → Promise / async/await / suspend / impl Future, so callers await instead of blocking on a semaphore.
  • WASM Component Model (WIT) emitter — emit a .wit so any host consumes the interface without bespoke glue.