mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-30 21:29:33 +00:00
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:
parent
965fd68785
commit
7902fa050b
3
examples/timer/go_bindings/.gitignore
vendored
Normal file
3
examples/timer/go_bindings/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/libmy_timer.dylib
|
||||
/libmy_timer.so
|
||||
/example/example
|
||||
34
examples/timer/go_bindings/Makefile
Normal file
34
examples/timer/go_bindings/Makefile
Normal 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
|
||||
58
examples/timer/go_bindings/README.md
Normal file
58
examples/timer/go_bindings/README.md
Normal 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.
|
||||
7
examples/timer/go_bindings/example/go.mod
Normal file
7
examples/timer/go_bindings/example/go.mod
Normal file
@ -0,0 +1,7 @@
|
||||
module example
|
||||
|
||||
go 1.21
|
||||
|
||||
require my_timer v0.0.0
|
||||
|
||||
replace my_timer => ../
|
||||
70
examples/timer/go_bindings/example/main.go
Normal file
70
examples/timer/go_bindings/example/main.go
Normal 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")
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user