Kinda is an Elixir package using Zig to bind a C library to BEAM the Erlang virtual machine. The core idea here is using comptime features in Zig to create a "resource kind" which is "higher-kinded" type abstracts the NIF resource object, C type and Elixir module.
The general source code generating and building approach here is highly inspired by the TableGen/.inc in LLVM. Kinda will generate NIFs exported by resource kinds and and provide Elixir macros to generate higher level API to call them and create resource. With Kinda, NIFs generated and hand-rolled co-exist and complement each other.
If available in Hex, the package can be installed
by adding Kinda
to your list of dependencies in mix.exs
:
def deps do
[
{:kinda, "~> 0.7.1"}
]
end
Documentation can be generated with ExDoc and published on HexDocs. Once published, the docs can be found at https://hexdocs.pm/Kinda.
A full example could be found in kinda_example
define a root module for the C library
defmodule Foo.CAPI do
use Kinda.Library, kinds: [Foo.BarKind]
end
define a forwarder module
defmodule Foo.Native do
use Kinda.Forwarder, root_module: CAPI
end
define kinds
defmodule Foo.BarKind do
use Kinda.ResourceKind,
forward_module: Foo.Native
end
Packing anything into a resource
Almost all C++/Rust implementation seems to force you to map a fixed size type to a resource type.
In fact for same resource type, you can have Erlang allocate memory of any size.
With Zig's comptime sizeof
you can easily pack a list of items into an array/struct without adding any abstraction and overhead. An illustration:
[(address to item1), item1, item2, item3, ...]
So the memory is totally managed by Erlang, and you can use Zig's comptime feature to infer everything involved.
Saving lib/include path to a Zig source and use them in your build.zig
. You can use Elixir to find all the paths. It is way better than configuring with make/CMake because you are using Elixir a whole programming language to do it. It is described in Zig doc as:
Surfacing build configuration as comptime values by providing a file that can be imported by Zig code.
Inter NIF resource exchange. Because it is Zig, just import the Zig source from another Hex package.
Kinda borrows a lot of good ideas and code from Zigler (Zigler is awesome~) but there are some differences:
build.zig
. So if you want to also sneak CMake/Bazel inside, go for it.Kinda is also inspired by Rustler. Rustler really define what a ergonomic NIF lib should be like.
nif_gen
and type_gen
)..h
files instead of .td
files.ResourceKind
: a Zig struct to bundle:
ResourceKind
to bundle one or more different ResourceKind
sroot_module
: the NIF module will load the C shared library
forward_module
: module implement functions like array/2
, ptr/1
to forward functions to root_module
. By implementing different callbacks, you might choose to use Elixir struct to wrap a resource or use it directly.
Recommended module mapping convention:
let's say you have a Elixir module to manage a C type. And the NIF module is SomeLib.CAPI
defmodule SomeLib.I64 do
defstruct ref: nil
def create() do
ref = apply(SomeLib.CAPI, Module.concat([__MODULE__, :create]) |> Kinda.check!
struct!(__MODULE__, %{ref: ref})
end
end
in SomeLib.CAPI
there should be a NIF generated by Kinda with name :"Elixir.SomeLib.I64.create"/0
wrapper: a .h
C header file including all the C types and functions you want to use in Elixir. Kinda will generate a NIF module for every wrapper file.
wrapped functions, C function with corresponding NIF function name, will be called in the NIF module.
kinds to generate: return type and argument types of every functions in wrapper will be generated. User will need to implement the behavior Kinda.CodeGen
for a type with a special name (usually it is C function pointer), or it is the type name in C source by default.
raw nifs: nifs doesn't follow involved in the resource kind naming convention. Insides these NIFs it is recommended to use NIF resource types registered by Kinda.
translation-c
to generate Zig source code from wrapper headerOut of the box Kinda supports generating and loading pre-built NIF library.
use Kinda.Prebuilt,
otp_app: :beaver,
base_url: "[URL]",
version: "[VERSION]"
It reuses code in rustler_precompiled to follow the same convention of checksum checks and OTP compatibility rules.
In Kinda, besides the main NIF library, there might be kinda-meta-*.ex
for functions signatures and multiple shared libraries the main NIF library depends on. (Zig doesn't support static linking yet so we have to ship shared ones. Related issue)
cd kinda_example
mix deps.get
mix test --force
mix compile --force