Ivan FB 3240ac0080
feat(host): {.ffiHost.} metadata + Go wrapper (increment 5)
5a: record {.ffiHost.} procs in a compile-time registry (FFIHostMeta /
ffiHostRegistry), populated by the macro, so generators can see host fns.

5b: the Go generator emits an idiomatic wrapper over the host C ABI:
- a single //export cgo trampoline backs every host fn; a cgo.Handle in
  userData selects the Go closure;
- the closure runs on a fresh GOROUTINE so the FFI thread is never blocked
  (the non-blocking contract), then answers via <lib>_host_complete by token;
- a per-host `Set<Name>(func(string) (string, error))` method registers it.

Validated end to end with `go run` (examples/host_demo): Go UseToken -> Nim
{.ffi.} handler -> await fetchToken {.ffiHost.} -> Go trampoline -> goroutine
runs the closure -> host_complete -> future resolves on the loop thread ->
"token[TOK-session]" back in Go. Timer's Go output is unchanged (no host fns);
its regenerated .h just gains the always-exported host ABI decls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 23:49:36 +02:00

38 lines
906 B
Go

// Go example for a {.ffiHost.} host callback.
//
// `fetchToken` is implemented HERE (the Go app) and registered with
// SetFetchToken. When we call UseToken, the Nim library calls back into this Go
// closure for a token — the closure runs on a goroutine the generated wrapper
// spawns (never blocking the FFI thread) and answers via host_complete.
package main
import (
"fmt"
"log"
hd "host_demo"
)
func main() {
node, err := hd.NewHost_demo()
if err != nil {
log.Fatalf("create: %v", err)
}
defer node.Destroy()
// The host's implementation of the {.ffiHost.} fetchToken.
node.SetFetchToken(func(key string) (string, error) {
return "TOK-" + key, nil
})
res, err := node.UseToken("session")
if err != nil {
log.Fatalf("useToken: %v", err)
}
fmt.Printf("result: %s\n", res)
if res != "token[TOK-session]" {
log.Fatalf("unexpected result: %q", res)
}
fmt.Println("OK")
}