So you made a cool Nim library but you want it to be available to other languages as well. With genny
you can generate a dynamically linked library with a simple C API and generated bindings for many languages. In some ways its similar to SWIG project for C or djinni for C++.
nimble install genny
This library has no dependencies other than the Nim standard library.
See Pixie's nimble file for an example of how to compile bindings with genny
.
Language | Method | Enums | Objects | Ref Objects | Seqs | GC |
---|---|---|---|---|---|---|
Nim | {.importc.} | ✅ | ✅ | ✅ | ✅ | ✅ |
Python | ctypes | ✅ | ✅ | ✅ | ✅ | ✅ |
Node.js | ffi-napi | ✅ | ✅ | ✅ | ✅ | no |
C | .h | ✅ | ✅ | ✅ | ✅ | no |
It would be pretty easy to just export a C API from Nim and have users call the ugly C style methods but that is not our goal with Genny. Instead, we try to generate a "nice" API for each language that feels like it was custom-made. This means, where possible, we:
- Use naming conventions that are familiar in the language (CamelCase, Snake_Case or Kebab-Case).
- Make sure regular
object
s are passed by value and behave simply. - Make
ref object
s behave like OOP objects with members, methods and constructors. - Generate helper methods like
==
orisNull
. - Export
seq[X]
as something that feels like a native array. - Export
seq[X]
on aref object
behaves like what we call a bound-seq. - Support the
[]
syntax. - Support the
.
member syntax. - Overload math operators
+
,-
,*
,/
. - Overload
proc
s, where we first unpack them to different C calls with unique prefixes and then repack them back into overloaded methods or functions. - Pass optional arguments.
- Pass enums and constants.
- Synchronize the binding language's GC and Nim's ARC GC.
- And even copy the comments so that automated tools can use them.
Genny provides a DSL that you can use to define how things need to be exported. The DSL is pretty simple to follow:
import genny, pixie
exportConsts:
defaultMiterLimit
autoLineHeight
exportEnums:
FileFormat
BlendMode
exportProcs:
readImage
readmask
readTypeface
exportObject Matrix3:
constructor:
matrix3
procs:
mul(Matrix3, Matrix3)
exportRefObject Mask:
fields:
width
height
constructor:
newMask(int, int)
procs:
writeFile(Mask, string)
copy(Mask)
getValue
setValue
# Must have this at the end.
writeFiles("bindings/generated", "pixie")
include generated/internal
See more in the pixie bindings
Genny generates a dynamic library C API for your Nim library and generates bindings for that dynamic library in many languages. To do this, things like proc overloads, complex types, sequences, and many other Nim features need to be addressed to make them work over a C interface.
To make that C interface, Genny makes assumptions about what your Nim source looks like and how to give overloaded procedures unique names. This may not work out of the box for every Nim library yet!
This version of Genny was created to generate bindings for Pixie. You can see how Pixie's dynamic library API is exported and the bindings generated in this file and the results here.
Nim is a niche language. We believe we can broaden Nim's appeal by creating Nim libraries for other more popular language and have Nim slowly work its way into companies. Maybe after companies see that they already use Nim, they will start writing their own code in it.
"Can't you just import your cool library in Nim?" We thought it was important to test the library in a what we call Nim-C-Nim sandwich. It makes sure everyone uses your library API the same way. This also means you could ship huge Nim libraries as DLLs and use them in your Nim programs without recompiling everything every time.