docs(examples): add native Go example exercising struct params

A runnable main.go that constructs the timer with a TimerConfig, then calls
Echo (struct param), Complex (slice-of-structs + slice + two optionals) and
Schedule (three struct params) with idiomatic Go values — the methods the Go
generator used to skip. The Makefile builds the dylib next to the package
(cgo's ${SRCDIR} rpath finds it at runtime); README documents the Nim->Go type
mapping. Verified end-to-end with `go run`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ivan FB 2026-05-31 11:03:22 +02:00
parent 965fd68785
commit 7902fa050b
No known key found for this signature in database
GPG Key ID: DF0C67A04C543270
5 changed files with 172 additions and 0 deletions

3
examples/timer/go_bindings/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/libmy_timer.dylib
/libmy_timer.so
/example/example

View File

@ -0,0 +1,34 @@
# Build the Nim dylib next to the generated Go package and run the example.
#
# make run # build libmy_timer + run the example
# make clean
#
# The generated package's cgo directives use ${SRCDIR}, so the library only has
# to sit in this directory (-L/-rpath point here). It is compiled from the repo
# root so the vendored Nimble dependencies resolve.
REPO_ROOT := $(abspath ../../..)
NIM_SRC := $(REPO_ROOT)/examples/timer/timer.nim
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
LIBNAME := libmy_timer.dylib
else
LIBNAME := libmy_timer.so
endif
NIMFLAGS := --mm:orc -d:chronicles_log_level=WARN --app:lib --noMain \
--nimMainPrefix:libmy_timer
.PHONY: all run clean
all: $(LIBNAME)
$(LIBNAME):
cd $(REPO_ROOT) && nim c $(NIMFLAGS) -o:$(CURDIR)/$(LIBNAME) $(NIM_SRC)
run: $(LIBNAME)
cd example && go run .
clean:
rm -f $(LIBNAME) example/example

View File

@ -0,0 +1,58 @@
# Go (cgo) bindings — native (same-process) example
Generated cgo bindings for the timer library. The Go wrapper links the library
directly and calls the **native** C ABI, marshalling each `{.ffi.}` type from an
idiomatic Go struct into its flat C-POD form per call.
## Files
| File | Description |
|------|-------------|
| `my_timer.go` | Generated cgo package. One Go struct per `{.ffi.}` type plus a `toC()` marshaller; one method per `{.ffi.}` proc. |
| `my_timer.h` | Native C header (emitted alongside the `.go` so cgo's `#include` resolves locally). |
| `go.mod` | Makes the package an importable module. |
| `example/` | A runnable `main.go` that exercises the struct-param methods. |
Regenerate with `nimble genbindings_go` (from the repo root); the files here are
overwritten each time and `gofmt`-finalized.
## Mapping
| Nim (`{.ffi.}`) | Go |
|-----------------|-----|
| `string` | `string` |
| `int` / `int64` … | `int64` … |
| `bool` | `bool` |
| `seq[T]` | `[]T` |
| `Option[T]` / `Maybe[T]` | `*T` (nil = none) |
| nested `{.ffi.}` struct | nested Go struct |
Each call deep-copies its arguments across the FFI thread, so the Go-side C
allocations are freed (via `defer`) as soon as the call returns. String-returning
methods give back a Go `string`; struct-returning methods deliver their CBOR
encoding (decode it with a Go CBOR library if needed).
## Build & run
```sh
cd examples/timer/go_bindings
make run
```
Expected output:
```
created timer
version: nim-timer v0.1.0
echo: ok (struct param round-tripped)
complex: ok (seq/option graph deep-copied)
schedule: ok (three struct params in one call)
done
```
`Echo`, `Complex` and `Schedule` take `{.ffi.}` structs, slices and optionals
directly — previously these procs were skipped by the Go generator.
For the cross-process / cross-machine path (CBOR over a socket), see
[`../ipc`](../ipc); a Go client could speak the same wire protocol using any Go
CBOR library.

View File

@ -0,0 +1,7 @@
module example
go 1.21
require my_timer v0.0.0
replace my_timer => ../

View File

@ -0,0 +1,70 @@
// Native (same-process) Go example for the timer library.
//
// The generated cgo package marshals each {.ffi.} struct param into its flat
// C-POD form and passes it to the native ABI by value — so methods that take
// structs, sequences and optionals (Echo, Complex, Schedule) are now callable
// directly with idiomatic Go types. String-returning calls (Version) come back
// as a Go string; struct-returning calls deliver their CBOR encoding.
package main
import (
"fmt"
"log"
timer "my_timer"
)
func strp(s string) *string { return &s }
func intp(i int64) *int64 { return &i }
func main() {
// Construct with a TimerConfig struct.
node, err := timer.NewMy_timer(timer.TimerConfig{Name: "go-native-demo"})
if err != nil {
log.Fatalf("create: %v", err)
}
defer node.Destroy()
fmt.Println("created timer")
// String return.
if v, err := node.Version(); err == nil {
fmt.Printf("version: %s\n", v)
}
// Struct param: EchoRequest { Message string; DelayMs int64 }.
if _, err := node.Echo(timer.EchoRequest{Message: "hello from Go", DelayMs: 5}); err != nil {
log.Printf("echo: %v", err)
} else {
fmt.Println("echo: ok (struct param round-tripped)")
}
// Deeply nested: slice of structs, slice of strings, two optionals.
_, err = node.Complex(timer.ComplexRequest{
Messages: []timer.EchoRequest{
{Message: "one", DelayMs: 0},
{Message: "two", DelayMs: 0},
},
Tags: []string{"a", "b"},
Note: strp("a note"),
Retries: intp(3),
})
if err != nil {
log.Printf("complex: %v", err)
} else {
fmt.Println("complex: ok (seq/option graph deep-copied)")
}
// Multiple struct params at once.
_, err = node.Schedule(
timer.JobSpec{Name: "nightly", Payload: []string{"x"}, Priority: 5},
timer.RetryPolicy{MaxAttempts: 3, BackoffMs: 100, RetryOn: []string{"timeout"}},
timer.ScheduleConfig{StartAtMs: 1000, IntervalMs: 5000, Jitter: intp(50)},
)
if err != nil {
log.Printf("schedule: %v", err)
} else {
fmt.Println("schedule: ok (three struct params in one call)")
}
fmt.Println("done")
}