Enif-ffi 0.1.0 - raw Rust FFI bindings to the Erlang NIF API

I’ve just published enif-ffi 0.1.0 — a thin, 1:1, all-unsafe Rust binding to the erl_nif C API.

It deliberately adds nothing of its own. It’s the foundation you’d build a safe NIF library on top of — or use directly if you want the bare enif_* surface with nothing in the way.

What it gives you

  • Direct bindings to every enif_* function and ERL_NIF_* / ErlNif* type and constant. The C prefixes are dropped and the whole surface is re-exported flat: enif_make_atom → make_atom, ERL_NIF_TERM → Term, ErlNifEnv → Env.
  • A nif_init! macro that generates the platform-correct entry point, so your NIF source is identical on Unix (symbols resolved via dlsym at load) and Windows (the BEAM-supplied callback table).
  • Thoroughly documented. Every function, type, constant, and flag has a hand-written description in the crate’s own terms (not copied from the C headers), the corresponding enif_* argument and return semantics, a link to the upstream erl_nif docs, and the NIF/OTP version it was introduced in — with the trickier behaviors (e.g. enif_hash’s phash2 27-bit result) verified against the OTP source itself.

Version-gated by OTP release

  • Floor is NIF 2.15 (OTP 22), always compiled. Newer API is opt-in through an additive feature ladder: nif_2_16 (OTP 24), nif_2_17 (OTP 26), nif_2_18 (OTP 29).
  • Every function, type, and constant is annotated with the exact NIF version and OTP release it was introduced in, derived from the tagged erl_nif header history rather than guesswork — and documented with a link back to the upstream erl_nif docs.

Verified on real BEAMs

  • A smoke NIF is built and loaded into actual BEAMs across NIF 2.15–2.18 on both Unix and Windows in CI, proving the binding resolves and dispatches on every supported version. The function-pointer table layout is additionally audited against the tagged header history.

Who it’s for

Honestly: most people writing NIFs want a safe, higher-level library. enif-ffi is the layer beneath that — reach for it if you’re building such a layer, or if you want raw, unmediated control over the table. On the Erlang side it’s the same as any NIF; you just erlang:load_nif/2 and call it.

Links

It’s an early 0.1.0 — the surface may still shift (very slightly) before 1.0, and erl_nif being additive means growth should stay within minor bumps. Feedback very welcome, especially from anyone who’s written NIFs by hand.

1 Like