nimler

Nimler is a library for authoring Erlang and Elixir NIFs in the nim programming language. It has mostly complete bindings for the Erlang NIF API and some accessories for making writing NIFs easier, including idiomatic functions for converting between Erlang terms and nim types, and simplifications for using resource objects.

Mostly, Nimler is a minimal, zero-dependency wrapper for Erlang NIF API.

TargetStatus
x86_64-linux
arm64-linux

Sample

Positional API sample:

import nimler

using
  env: ptr ErlNifEnv

func add(env; a: int, b: int): (ErlAtom, int) {.xnif.} =
  (AtomOk, a + b)
  
func sub(env; a: int, b: int): (ErlAtom, int) {.xnif.} =
  (AtomOk, a - b)

func mul(env; a: int, b: int): (ErlAtom, int) {.xnif.} =
  (AtomOk, a * b)
  
exportNifs "Elixir.NifMath", [ add, sub, mul ]

Raw API

If porting a NIF from C, it may be easier to start with the raw API, which retains NIF signature:

import nimler

using
  env: ptr ErlNifEnv
  argc: cint
  argv: ptr UncheckedArray[ErlNifTerm]

func add(env, argc, argv): ErlNifTerm {.nif, arity: 2.} =
  # `fromTerm(env, term, to_type)` returns a nim Option.
  let a1 = fromTerm(env, argv[0], int).get(0)
  let a2 = fromTerm(env, argv[1], int).get(0)
  return toTerm(env, a1 + a2)
  
# Specify external NIF name. In this case, the function is exported as "sub"
# rather than "subnums" 
func subnums(env, argc, argv): ErlNifTerm {.nif, arity: 2, name: "sub".} =
  let a1 = fromTerm(env, argv[0], int).get(0)
  let a2 = fromTerm(env, argv[1], int).get(0)
  return toTerm(env, a1 - a2)

# This NIF is tagged with `dirty_cpu`. See the documentation for details on
# using dirty schedulers: https://erlang.org/doc/man/erl_nif.html#functionality
func mul(env, argc, argv): ErlNifTerm {.nif, arity: 2, dirty_cpu.} =
  let a1 = fromTerm(env, argv[0], int).get(0)
  let a2 = fromTerm(env, argv[1], int).get(1)
  return toTerm(env, a1 * a2)

# Optional `{.raises: [].}` pragma is part of nim's effect system. This verifies
# that the NIF does not raise an exception
func div(env, argc, argv): ErlNifTerm {.nif, arity: 2, raises: [].} =
  let a1 = fromTerm(env, argv[0], int).get(0)
  let a2 = fromTerm(env, argv[1], int)

  if a2 == some(0.int):
    return enif_make_badarg(env)

  return toTerm(env, a1 div a2.unsafeGet())
  
exportNifs("Elixir.NifMath", [
  add,
  subnums,
  mul,
  div
])