otter/lib.rs
1// Render the "Available on crate feature X" labels on docs.rs (and any
2// `RUSTDOCFLAGS="--cfg docsrs"` nightly doc build). The `feature(doc_cfg)` gate is
3// emitted only when `--cfg docsrs` is set, so ordinary stable builds never see it.
4// `auto_cfg = false` suppresses rustdoc's automatic pills for *every* `#[cfg]`
5// (which would otherwise leak std-internal cfgs like `no_global_oom_handling` and
6// platform gates onto our pages); only the explicit `#[doc(cfg(...))]` annotations
7// on the feature-gated surface show.
8#![cfg_attr(docsrs, feature(doc_cfg))]
9#![cfg_attr(docsrs, doc(auto_cfg = false))]
10//! ## Overview
11//!
12//! **Write fast and efficient Erlang NIFs in Rust**
13//!
14//! `otter` is built from the ground up to give Erlang and Elixir programmers an
15//! easy, efficient way to write NIFs in Rust. The design is driven by two
16//! foundational principles:
17//! 1. **Functionality** — `otter`'s first priority is an API surface that
18//! faithfully captures the capabilities of the underlying `erl_nif` C API — and
19//! where the safe surface isn't enough, the raw API can be exposed by enabling
20//! the `raw` feature.
21//! 2. **Speed** — One of the prime motivations for writing NIFs is to achieve
22//! speed and memory efficiency not possible in the Erlang VM. `otter`'s
23//! datatypes are designed to "pay only for what you need" through controlled
24//! layering of `enif_*` calls, with extensive work to employ compile-time
25//! constraints rather than runtime ones.
26//!
27//! ## Quickstart
28//!
29//! ```no_run
30//! use otter::types::CallEnv;
31//!
32//! // Declare the module: name, NIF table, and any atoms to pre-intern.
33//! otter::init!("my_nif", [add], atoms = [ok, error]);
34//!
35//! // A NIF that adds two integers, decoded straight to `i64` and encoded back.
36//! #[otter::nif]
37//! fn add(_env: CallEnv<'_>, a: i64, b: i64) -> i64 {
38//! a + b
39//! }
40//! ```
41//!
42//! ## Features
43//!
44//! A few highlights of the surface; the [guided tour](#a-guided-tour) below maps
45//! the whole crate.
46//!
47//! ### Generative env/term lifetimes
48//!
49//! Each term is branded by the env that produced it. [`Env<'id>`](types::Env) is
50//! a sealed trait whose lifetime `'id` is a *generative brand* — a fresh,
51//! non-escaping marker minted at every entry point. Every term type carries that
52//! brand (`Integer<'id>`, `Binary<'id>`, …), so a term from one env cannot be
53//! used with another: the compiler rejects it, with no per-operation runtime
54//! check. The BEAM treats a cross-env term as undefined behavior, and this
55//! construction makes that mistake impossible to write. (This is the
56//! [generativity pattern].)
57//!
58//! ### Layered term resolution
59//!
60//! Reading a term through the C API is a cascade: `enif_term_type` and the
61//! `enif_is_*` family reveal a type, `enif_get_tuple` hands back elements that
62//! are themselves opaque terms, and so on — a full decode can cost many calls
63//! into the VM, most of them unnecessary. `otter` mirrors that progression in its
64//! types, so you stop at exactly the depth you need: [`AnyTerm`](types::AnyTerm)
65//! → [`TypedTerm`](types::TypedTerm) → a concrete term type → a native Rust
66//! value.
67//!
68//! Resolution is lazy. Constructing a term is free — one machine word plus a
69//! zero-sized brand marker — and nothing is read off the BEAM heap until an
70//! env-passing accessor asks for it:
71//!
72//! ```no_run
73//! # use otter::types::{CallEnv, Atom, Binary, Integer, Map};
74//! # fn demo(env: CallEnv<'_>, key_atom: Atom) {
75//! // Constructors are associated functions taking an env:
76//! let i = Integer::from_i64(env, 42);
77//! let b = Binary::from_bytes(env, b"hello");
78//! let m = Map::new(env);
79//!
80//! // Accessors take an env:
81//! let n = i.to_i64(env); // Option<i64>
82//! let bytes = b.as_bytes(env); // &[u8], zero-copy into the BEAM heap
83//!
84//! // Term inputs are taken as `impl Term<'id>` — pass concrete types directly,
85//! // no `.encode()` needed; a term of the wrong brand fails to compile:
86//! let m = m.put(env, key_atom, i);
87//! # let _ = (n, bytes, m);
88//! # }
89//! ```
90//!
91//! ### Honest codecs
92//!
93//! [`Encoder`](codec::Encoder)/[`Decoder`](codec::Decoder) move between terms and
94//! native Rust types — integers, floats, `bool`, `str`/`String`, tuples,
95//! `Vec<T>`, `HashMap<K, V>`, and (with the `bigint` feature) arbitrary-precision
96//! integers. A decoder never lies: `300` into a `u8` is an `IntegerOverflow`
97//! error, not a silently truncated `44`, and a float will not quietly swallow an
98//! integer. A failed decode on a NIF argument becomes `badarg`; a failed encode
99//! on a return becomes `badret`.
100//!
101//! ### Faithful Erlang types
102//!
103//! All eleven term types are present, and they mirror Erlang rather than a
104//! convenient subset of it. [`List`](types::List) is a real cons cell
105//! (`Nil | Cell(head, tail)`), so improper lists are first-class and a codec
106//! rejects a bad tail with a clean error instead of papering over it.
107//!
108//! ### The full send matrix
109//!
110//! Message passing is a 2×2 of four free verbs: `send_copy`/`send_move` — copy a
111//! live term, or steal an [`OwnedEnvArena`](types::OwnedEnvArena) heap in O(1) —
112//! each available plain (NULL caller, off-thread) or `_from` (caller-attributed,
113//! in-NIF). You can build a message on a worker thread with no env in hand and
114//! move its whole heap into a process in a single send.
115//!
116//! ### Hot code upgrade without cross-build ABI assumptions
117//!
118//! Every `otter` module is hot-code-upgradeable. Erlang upgrades a running system
119//! in place: a second build of a NIF library can load beside the first and
120//! inherit its live state (resources, `priv_data`). The two builds may come from
121//! different compilers and allocators and need not be byte-identical source, so
122//! **the upgrade boundary is a foreign-ABI boundary.**
123//!
124//! Outside the `raw` feature, `otter` never assumes across that boundary that
125//! allocators or drop glue are compatible, that std datatypes share a layout, or
126//! that identical source compiles to a compatible layout; the safe path holds by
127//! construction. Code that relies on cross-build ABI compatibility belongs only
128//! behind `raw`, where the caller takes responsibility. The full treatment is in
129//! the project's `docs/UPGRADE.md`.
130//!
131//! ### The raw escape hatch
132//!
133//! Where the safe surface isn't enough, the `raw` feature exposes the
134//! all-`unsafe` [`enif_ffi`] crate directly, widens the brand-bridging term
135//! constructors to `pub`, and accepts the `_raw` lifecycle callbacks in
136//! [`init!`](macro@init) — a deliberate, opt-in boundary where you take
137//! responsibility for the ABI. See [Cargo features](#cargo-features).
138//!
139//! ## A guided tour
140//!
141//! The crate is organized into a few logical areas:
142//!
143//! - **The env/term spine** — [`types`] defines the [`Env`](types::Env) trait and
144//! its concrete kinds ([`CallEnv`](types::CallEnv), [`InitEnv`](types::InitEnv),
145//! [`CallbackEnv`](types::CallbackEnv), [`DeinitEnv`](types::DeinitEnv),
146//! [`OwnedEnv`](types::OwnedEnv)), the [`Term`](types::Term) trait and its
147//! resolution levels, the [`OwnedEnvArena`](types::OwnedEnvArena), the four
148//! message [send verbs](types#functions), and the [`Raised`](types::Raised)
149//! exception witness.
150//! - **Term types** — [`types::terms`] has one concrete type per Erlang type:
151//! [`Atom`](types::Atom), [`Integer`](types::Integer), [`Float`](types::Float),
152//! [`Binary`](types::Binary)/[`Bitstring`](types::Bitstring),
153//! [`List`](types::List), [`Tuple`](types::Tuple), [`Map`](types::Map),
154//! [`Pid`](types::Pid), [`Port`](types::Port), [`Reference`](types::Reference),
155//! and [`Fun`](types::Fun).
156//! - **Owned buffers** — [`BinaryBuf`](types::BinaryBuf), a growable byte buffer
157//! you fill off-thread with no env in hand, then hand to the BEAM in one move.
158//! - **Codecs** — [`codec`] holds the
159//! [`Encoder`](codec::Encoder)/[`Decoder`](codec::Decoder) traits and their
160//! impls for the native Rust types.
161//! - **Resources and per-build state** — [`resource`] gives
162//! [`ResourceArc<T>`](resource::ResourceArc), a refcounted handle to a Rust
163//! value the BEAM can hold and hand back; [`priv_data`] is the per-build
164//! private-data slot and the resource-type registry.
165//! - **Runtime services** — [`time`] (BEAM monotonic clock), [`system`]
166//! (thread-type introspection), [`select`] (I/O event multiplexing), and
167//! [`alloc`] (route Rust's global allocator through the BEAM allocator).
168//! - **Macros** — [`nif!`](macro@nif) marks a function as a NIF,
169//! [`init!`](macro@init) declares the module entry point, and [`atom!`]
170//! retrieves a pre-interned atom.
171//!
172//! ## Cargo features
173//!
174//! All features are off by default; `otter` always binds NIF 2.17 (OTP 26).
175//!
176//! | Feature | Effect |
177//! |---|---|
178//! | `nif_2_18` | Enable the NIF 2.18 (OTP 29) additions. |
179//! | `bigint` | Pull in the optional `num-bigint` dependency (compiled only when this feature is enabled) and add arbitrary-precision integers: `types::BigInt` (a re-export of `num_bigint::BigInt`), its `Encoder`/`Decoder`, and `Integer::to_bigint`/`from_bigint`. |
180//! | `raw` | Expose the raw, all-`unsafe` [`enif_ffi`] crate as the escape hatch, widen the brand-bridging term constructors to `pub`, and accept the `_raw` lifecycle callbacks in [`init!`](macro@init). |
181//!
182//! [generativity pattern]: https://arhan.sh/blog/the-generativity-pattern-in-rust/
183pub mod types;
184pub mod codec;
185pub mod resource;
186pub mod priv_data;
187mod abi;
188pub mod alloc;
189pub mod time;
190pub mod system;
191pub mod select;
192
193#[doc(hidden)]
194#[path = "__codegen.rs"]
195pub mod __codegen;
196
197// The raw, 1:1, all-unsafe `enif_ffi` crate — the escape hatch, available only
198// under the `raw` feature. Generated code no longer names this path (it uses
199// `__codegen::ffi::*`), and the enif types that appear in otter's own public API
200// are re-exported through their modules (`select::{Event, SelectFlags, …}`,
201// `types::{TermType, Hash, UniqueInteger}`, `time::*`, `system::SysInfo`,
202// `resource::ResourceFlags`), so nothing in the safe surface needs this. (enhance-11)
203#[cfg(feature = "raw")]
204#[cfg_attr(docsrs, doc(cfg(feature = "raw")))]
205pub use enif_ffi;
206
207// enif-ffi's `nif_init!` builds the platform entry point and resolves the
208// enif_* table at load. `#[macro_export]` macros aren't reachable through the
209// re-exported crate path (`otter::enif_ffi::nif_init!`), so re-export it by name
210// into otter's root; the `init!`-generated code invokes `::otter::nif_init!`.
211#[doc(hidden)]
212pub use enif_ffi::nif_init;
213
214pub use otter_codegen::nif;
215pub use otter_codegen::init;
216pub use otter_codegen::resource_impl;
217
218// Internal: widen an item to `pub` under the `raw` feature without duplicating
219// it. Used within otter to expose selected internals on the raw escape hatch;
220// the emitted `cfg(feature = "raw")` resolves against otter's own `raw` feature.
221#[doc(hidden)]
222pub use otter_codegen::raw;
223
224/// Retrieve an atom pre-declared in the `atoms = [...]` list of
225/// [`init!`](crate::init).
226///
227/// Returns an [`Atom`] via a single atomic load — no hash lookup, no NIF call.
228/// The atoms are interned once at NIF load (and re-interned on hot upgrade) by
229/// the `init!`-generated scaffolding, so they are ready before any NIF runs.
230///
231/// ```no_run
232/// # use otter::types::{Atom, CallEnv};
233/// otter::init!("my_nif", [get_ok], atoms = [ok, error]);
234///
235/// #[otter::nif]
236/// fn get_ok(_env: CallEnv<'_>) -> Atom {
237/// otter::atom![ok]
238/// }
239/// ```
240///
241/// [`Atom`]: crate::types::Atom
242#[macro_export]
243macro_rules! atom {
244 ($id:ident) => {
245 __otter_atoms::$id.get()
246 };
247}