rebar3_otter

Hex.pm License: MIT OR Apache-2.0

A rebar3 plugin that builds Rust NIF crates written with otter-nif and installs the resulting shared library where erlang:load_nif/2 expects it. Write NIFs in Rust, then let this plugin drive cargo from your normal rebar3 build.

The plugin has no Rust dependency of its own — it treats the Rust toolchain as an external tool, the same way rebar3 treats the Erlang compiler.

Requirements

  • OTP 27 — the plugin uses OTP-27 doc syntax (-moduledoc/-doc).
  • cargo on PATH — toolchain installation is the user's responsibility.

(The NIF runtime floor is lower — NIF 2.17 / OTP 26 — but that governs loading the compiled library into a BEAM, not this build-time plugin.)

Install

Add the plugin to your rebar.config:

{plugins, [rebar3_otter]}.

Declare the crates to build:

{otter_crates, [
    #{name => "my_nif", path => "native/my_nif"}
]}.

Each entry in otter_crates is a map describing one crate:

KeyRequired?TypeDescription
nameRequiredstring() | binary()Cargo crate name — must match [package].name in the crate's Cargo.toml.
pathRequiredstring() | binary()Crate directory, relative to the declaring app's directory.
modeOptionalrelease | debugCargo build profile. Defaults to release.
featuresOptional[string() | binary()]Cargo features to enable. Defaults to [].
targetOptionalstring() | binary() | undefinedCross-compilation target triple; undefined builds for the host. Defaults to undefined.

String-typed values (name, path, target, and each features element) accept a string or a binary, normalized to a string. They mirror Cargo's own strings and carry hyphens (e.g. "x86_64-unknown-linux-gnu") naturally.

Then wire the providers as build hooks:

{provider_hooks, [
    {pre, [
        {compile, otter_compile},
        {clean,   otter_clean}
    ]}
]}.

otter_crates is read per application: path resolves relative to that app's directory and the built .so is installed into that app's priv/native/. In an umbrella project each app declares the crates it owns.

Scaffold a crate

rebar3 otter new --name my_nif

Generates native/my_nif/ with a minimal Cargo.toml (depending on otter-nif) and a src/lib.rs containing a hello NIF. The Erlang loader is intentionally not generated — NIF loading is two lines of standard Erlang you should write yourself:

-module(my_nif).
-on_load(init/0).

init() ->
    erlang:load_nif(filename:join(code:priv_dir(my_app), "native/my_nif"), 0).

%% Stub replaced at load time by the NIF implementation
hello() -> exit(nif_not_loaded).

Providers

ProviderNamespaceRole
otter_compiledefaultpre_compile hook — runs cargo build, copies the cdylib into priv/native/.
otter_cleandefaultpre_clean hook — removes the installed .so and the crate's target/.
newotterrebar3 otter new --name <n> — scaffolds a NIF crate.

cargo runs with ERTS_INCLUDE_DIR set to the running ERTS's include dir, so the native build can locate erl_nif.h without extra configuration.

See DESIGN.md for the full provider design, artifact-path resolution, and error handling.

License

Licensed under either of

at your option.