1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
//! `enif-ffi` — raw FFI bindings to the Erlang NIF API (`erl_nif`).
//!
//! A thin, 1:1, all-`unsafe` binding to the `enif_*` C API exported by the
//! BEAM. It is the foundation a safe NIF library is built on; it adds no
//! abstractions of its own.
//!
//! # Naming
//!
//! Every C prefix is dropped — `enif_` (functions), `ERL_NIF_` (the term type,
//! macros, constants), and `ErlNif` (everything else) — because the whole API
//! lives under the `enif_ffi::` namespace and the prefix would be pure
//! redundancy:
//!
//! - `ERL_NIF_TERM` → [`Term`], `ErlNifEnv` → [`Env`], `ErlNifBinary` → [`Binary`]
//! - `enif_make_atom` → `make_atom`, `enif_is_atom` → `is_atom`
//! - `ERL_NIF_SELECT_READ` → [`SelectFlags::READ`]
//!
//! A bare name that would collide with a Rust keyword or a `std` prelude item
//! takes a trailing underscore: `ErlNifOption` → `Option_`, `enif_self` →
//! `self_`.
//!
//! The public surface is flat: everything is re-exported to the crate root.
//! Prefer namespaced use (`enif_ffi::Term`, `enif_ffi::make_atom`) over a glob
//! import.
//!
//! # Who this is for
//!
//! Most NIF authors want a *safe* library on top of this — writing NIFs by hand
//! against a raw, all-`unsafe` table is deliberate, low-level work. Reach for
//! `enif-ffi` directly when you are building that safe layer, or when you want
//! the bare `enif_*` surface with no abstraction in the way.
//!
//! # A minimal NIF
//!
//! A consumer touches three things: the C-ABI functions (the BEAM calling
//! convention), the [`Entry`] that registers them and carries the load-time
//! metadata, and [`nif_init!`] to define the entry point. The same source
//! compiles on Unix and Windows.
//!
//! ```no_run
//! use enif_ffi::*;
//! use std::ffi::c_int;
//!
//! // add(A, B) -> A + B — decode two integers, return their sum.
//! unsafe extern "C" fn nif_add(env: *mut Env, argc: c_int, argv: *const Term) -> Term {
//! if argc != 2 {
//! return unsafe { make_badarg(env) };
//! }
//! let args = unsafe { std::slice::from_raw_parts(argv, 2) };
//! let (mut a, mut b): (c_int, c_int) = (0, 0);
//! if unsafe { get_int(env, args[0], &mut a) } == 0
//! || unsafe { get_int(env, args[1], &mut b) } == 0
//! {
//! return unsafe { make_badarg(env) };
//! }
//! unsafe { make_int(env, a.wrapping_add(b)) }
//! }
//!
//! // mk_tuple() -> {ok, 42} — build a 2-tuple of an atom and an integer.
//! unsafe extern "C" fn nif_mk_tuple(env: *mut Env, _argc: c_int, _argv: *const Term) -> Term {
//! let ok = unsafe { make_atom(env, c"ok".as_ptr()) };
//! let n = unsafe { make_int(env, 42) };
//! unsafe { make_tuple2(env, ok, n) }
//! }
//!
//! // Build the library descriptor: the function table plus the version and name
//! // metadata the BEAM reads at load. Both the table and the `Entry` must outlive
//! // the call, so they are leaked.
//! fn build_entry() -> *const Entry {
//! let funcs = vec![
//! Func { name: c"add".as_ptr(), arity: 2, fptr: nif_add, flags: 0 },
//! Func { name: c"mk_tuple".as_ptr(), arity: 0, fptr: nif_mk_tuple, flags: 0 },
//! ]
//! .leak();
//!
//! Box::leak(Box::new(Entry {
//! major: MAJOR_VERSION,
//! minor: MINOR_VERSION,
//! name: c"example".as_ptr(),
//! num_of_funcs: funcs.len() as c_int,
//! funcs: funcs.as_mut_ptr(),
//! load: None,
//! reload: None,
//! upgrade: None,
//! unload: None,
//! vm_variant: VM_VARIANT.as_ptr(),
//! options: 1,
//! sizeof_resource_type_init: std::mem::size_of::<ResourceTypeInit>(),
//! min_erts: MIN_ERTS_VERSION.as_ptr(),
//! }))
//! }
//!
//! // Generates the platform-correct `nif_init` symbol the BEAM calls at load.
//! enif_ffi::nif_init!(build_entry);
//! ```
//!
//! On the Erlang side, once the module has loaded the library:
//!
//! ```erlang
//! example:add(2, 3). %=> 5
//! example:mk_tuple(). %=> {ok, 42}
//! ```
//!
//! `smoke_test/` in the repository is the full, CI-exercised version of this:
//! built and loaded into real BEAMs across every supported NIF version.
//!
//! # Version support
//!
//! The floor is **NIF 2.15 (OTP 22)** — always compiled. Newer API is opt-in
//! through an additive feature ladder, each rung pulling in the one below:
//!
//! - `nif_2_16` — OTP 24
//! - `nif_2_17` — OTP 26
//! - `nif_2_18` — OTP 29
//!
//! Because the rungs chain, the enabled set is always a contiguous prefix, so
//! each version-gated item carries exactly one `#[cfg]`. Enabling a rung means
//! "I require at least this OTP"; the symbols it adds are resolved at load time
//! and a BEAM older than the target will fail the load rather than misbehave.
//!
//! Item-level version annotations and the gating boundaries are derived from the
//! tagged `erl_nif` header history (the `otp-enif` snapshot repo).
//!
//! # Platform
//!
//! Unix and Windows are both supported; the binding mechanism is chosen at
//! compile time. On Unix the `enif_*` table is resolved with `dlsym` at load;
//! on Windows the BEAM passes a callback struct to `nif_init`, which is stored
//! instead. [`nif_init!`] generates the correctly-typed entry point for the
//! target, so a NIF's source is identical on both.
//!
//! # Verification
//!
//! Correctness rests on two checks rather than exhaustive per-symbol tests:
//! the `enif_*` table layout is audited against the tagged `erl_nif` header
//! history (every field version-gated and order-matched), and a smoke NIF is
//! built and loaded into real BEAMs across NIF 2.15–2.18 on both Unix and
//! Windows in CI, proving the binding resolves and dispatches the table on
//! every supported version.
// Every `unsafe fn` body must still mark its unsafe operations with an inner
// `unsafe` block (the default in edition 2024; required here on edition 2021).
// Platform-specific definitions: the load-time symbol resolver and the
// divergent `SysIOVec`. Exactly one is compiled. These are `pub` only so the
// exported `nif_init!` macro can name their `init` (and `TWinDynNifCallbacks`)
// from a downstream crate; `#[doc(hidden)]` keeps that machinery — `init`
// included — out of the documented surface.
compile_error!;
pub use *;
pub use *;
/// Define the NIF library's entry point.
///
/// Generates the `nif_init` symbol the BEAM calls at load — with the correct
/// signature for the target platform — resolves the `enif_*` table (`dlsym` on
/// Unix, the BEAM-supplied callback table on Windows), then calls `$builder`,
/// your platform-agnostic function returning the library descriptor. The symbol
/// resolution is handled entirely inside the generated entry point; nothing else
/// in the crate needs to be called to wire the binding up.
///
/// `$builder` must be a `fn() -> *const `[`Entry`](crate::Entry). It runs once
/// during load, after the table is resolved, so it (and any wrapper it calls)
/// can use the `enif_*` API.
///
/// ```no_run
/// # use enif_ffi::*;
/// enif_ffi::nif_init!(build_entry);
///
/// fn build_entry() -> *const enif_ffi::Entry {
/// // build and register your functions, then leak a 'static `Entry`;
/// // see the crate-level example, or `smoke_test/` for a complete one
/// # Box::leak(Box::new(Entry {
/// # major: MAJOR_VERSION,
/// # minor: MINOR_VERSION,
/// # name: c"example".as_ptr(),
/// # num_of_funcs: 0,
/// # funcs: std::ptr::null_mut(),
/// # load: None,
/// # reload: None,
/// # upgrade: None,
/// # unload: None,
/// # vm_variant: VM_VARIANT.as_ptr(),
/// # options: 1,
/// # sizeof_resource_type_init: std::mem::size_of::<ResourceTypeInit>(),
/// # min_erts: MIN_ERTS_VERSION.as_ptr(),
/// # }))
/// }
/// ```