2026-07-09 14:51:42 -03:00
..
2026-07-09 14:51:42 -03:00
2026-07-09 14:51:42 -03:00
2026-07-09 14:51:42 -03:00
2026-07-09 14:51:42 -03:00

skeleton — new-library template

A minimal, compilable nim-ffi library: a constructor, one async method, a library-initiated event, and a destructor, plus a nimble file and checked-in C++ bindings. Copy this directory instead of reverse-engineering examples/timer/ (which is a feature tour, not a starting point).

What's here

  • skeleton.nim — the library. Every nim-ffi lib needs these four pieces, and nothing else:
    • declareLibrary("skeleton", Skeleton) — names the lib and its state object; must come before any FFI annotation.
    • {.ffiCtor.} constructor returning Future[Result[T, string]].
    • one {.ffi.} async method, same return contract.
    • {.ffiEvent.} for a library-initiated callback.
    • {.ffiDtor.} destructor.
    • genBindings() as the last top-level call.
  • skeleton.nimblebuild and genbindings_cpp tasks.
  • cpp_bindings/ — generated C++ bindings, checked in and diff-verified by CI (nimble check_bindings).

Copy-rename checklist

Pick your library name in two casings: a snake_case wire name (e.g. my_lib) and its PascalCase state type (e.g. MyLib).

  1. Copy the directory: cp -r examples/skeleton examples/my_lib
  2. Rename both files: skeleton.nimmy_lib.nim, skeleton.nimblemy_lib.nimble.
  3. In my_lib.nim, replace every skeletonmy_lib and SkeletonMyLib. That covers declareLibrary("my_lib", MyLib), the state type, and the myLibCreate / myLibHello / my_lib_destroy proc names. Rename the request/response/event/config types to whatever your API needs.
  4. In my_lib.nimble, update packageName, description, and every libskeletonlibmy_lib / skeleton.nimmy_lib.nim. The --nimMainPrefix:libmy_lib must match your declareLibrary name.
  5. Regenerate bindings: cd examples/my_lib && nimble genbindings_cpp (add -d:targetLang=rust / c / c_abi variants as needed — see the root ffi.nimble for the full flag set).
  6. If you keep the library long-term, wire it into the root ffi.nimble check_bindings task the same way skeleton is, so CI catches binding drift.

Contracts worth remembering

  • Every {.ffi.} / {.ffiCtor.} returns Future[Result[T, string]]: return ok(value) on success, return err("reason") on failure.
  • A {.ffi.} method takes the state object as its first parameter.
  • The dtor's exported symbol name (skeleton_destroy) avoids the camelCase→snake_case derivation and reads naturally in C; name yours the same way.
  • genBindings() reads compile-time registries populated by the pragmas above it. Anything declared after it — or in a module imported after it — is silently missing from the output.

Build & generate

cd examples/skeleton
nimble genbindings_cpp   # writes cpp_bindings/