While Elixir excels at concurrent and distributed systems, sometimes you need to tap into other languages for CPU-intensive tasks or to leverage existing libraries. Elixir provides several ways to integrate with other languages.
Rust is the most ergonomic choice for writing NIFs, thanks to the Rustler library. It provides memory safety and excellent performance.
# In mix.exs
def deps do
[
{:rustler, "~> 0.30.0"}
]
end
# In lib/native.ex
defmodule MyApp.Native do
use Rustler, otp_app: :my_app, crate: "native"
# These functions are implemented in Rust
def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
def fibonacci(_n), do: :erlang.nif_error(:nif_not_loaded)
end
// In native/native/src/lib.rs
#[rustler::nif]
fn add(a: i64, b: i64) -> i64 {
a + b
}
#[rustler::nif]
fn fibonacci(n: i64) -> i64 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2)
}
}
rustler::init!("Elixir.MyApp.Native", [add, fibonacci]);
NIFs with C require more careful memory management but offer maximum performance.
# In lib/nif.ex
defmodule MyApp.Nif do
@on_load :load_nifs
def load_nifs do
:erlang.load_nif('./priv/nif', 0)
end
def calculate_square(_x), do: raise "NIF not loaded"
end
// In c_src/nif.c
#include "erl_nif.h"
static ERL_NIF_TERM calculate_square(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
int x;
if (!enif_get_int(env, argv[0], &x)) {
return enif_make_badarg(env);
}
return enif_make_int(env, x * x);
}
static ErlNifFunc nif_funcs[] = {
{"calculate_square", 1, calculate_square}
};
ERL_NIF_INIT(Elixir.MyApp.Nif, nif_funcs, NULL, NULL, NULL, NULL)
Python integration typically uses Ports for safety and simplicity:
defmodule MyApp.Python do
def run_script(script, args) do
port = Port.open({:spawn, "python3 #{script} #{args}"}, [:binary])
receive do
{^port, {:data, result}} -> {:ok, result}
other -> {:error, other}
end
end
# Example: Call a Python ML model
def predict(data) do
run_script("predict.py", Jason.encode!(data))
end
end
# predict.py
import sys
import json
from my_ml_model import predict
input_data = json.loads(sys.argv[1])
result = predict(input_data)
print(json.dumps(result))
Zig provides a modern alternative to C for writing NIFs, with better safety features:
// In src/main.zig
const std = @import("std");
const beam = @import("beam");
export fn add(env: beam.env, argc: c_int, argv: [*c]const beam.term) beam.term {
var a: i64 = undefined;
var b: i64 = undefined;
beam.get_i64(env, argv[0], &a) catch return beam.raise_badarg(env);
beam.get_i64(env, argv[1], &b) catch return beam.raise_badarg(env);
return beam.make_i64(env, a + b);
}
✅ Do:
❌ Don’t:
Method | Performance | Safety | Use Case |
---|---|---|---|
NIFs | Fastest | Unsafe | CPU-intensive, small operations |
Ports | Moderate | Safe | I/O, external services |
C Nodes | Slow | Safe | Distributed systems |