Integrating Other Languages with Elixir

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.

Integration Methods

Rust Integration

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]);

C Integration with NIFs

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

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 Integration

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);
}

Best Practices

Do:

Don’t:

Performance Considerations

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

Common Pitfalls