Elixir FFI with Rust

+

Hans Elias Bukholm Josephsen - PolyConf 17

Elixir

defmodule HelloWorld do

  def hello(name) do
    IO.puts "Hello, #{name}!"
  end

end

HelloWorld.hello("PolyConf")

hello.exs

$ elixir hello.exs
Hello, PolyConf!

Concurrency

Distribution

Fault-tolerance

Supervisor

Worker

How does a process run?

Talking to the outside

  • Ports
  • Foreign Nodes
  • Port drivers
  • Native Implemented Functions (NIFs)

Very bad things can easily happen.

Rust

fn hello(name: &str) {
    println!("Hello, {}!", name);
}

fn main() {
    hello("PolyConf");
}
Hello, PolyConf!
  • zero-cost abstractions
  • move semantics
  • guaranteed memory safety
  • threads without data races
  • trait-based generics
  • pattern matching
  • type inference
  • minimal runtime
  • efficient C bindings
  • zero-cost abstractions
  • move semantics
  • guaranteed memory safety
  • threads without data races
  • trait-based generics
  • pattern matching
  • type inference
  • minimal runtime
  • efficient C bindings
  • Safety
  • Ease of use
  • Interoperability
  • Type composition
  • Resource objects

API Structure

fn add<'a>(env: NifEnv<'a>, args: &[NifTerm<'a>]) -> NifResult<NifTerm<'a>> {
    let num1: i64 = args[0].decode()?;
    let num2: i64 = args[1].decode()?;

    Ok((atoms::ok(), num1 + num2).encode(env))
}

Build process

When to use?

  • IO
  • Native APIs
  • Native libraries
  • Performance

Caveats

  • Rust installation
  • NIF execution time

Html5ever

Part of the servo project

Spec compliant HTML5 parser

Juicy

Iterative parser

Comparable in speed to jiffy

Stream parsing mode

Rox

RocksDB binding

By Urbint

Creating a NIF

$ mix rustler.new
This is the name of the Elixir module the NIF module will be 
registered to.
Module name > Testing.MyNif
This is the name used for the generated Rust crate. The default 
is most likely fine.
Library name (testing_mynif) > mynif
* creating native/mynif/README.md
* creating native/mynif/Cargo.toml
* creating native/mynif/src/lib.rs
Ready to go! See /home/hansihe/git/testing/native/mynif/README.md 
for further instructions.
$
defmodule Testing.Mixfile do
  use Mix.Project

  def project do
    [
      [...]

      compilers: [:rustler] ++ Mix.compilers(),
      rustler_crates: rustler_crates(),

      [...]
    ]
  end

  def rustler_crates do
    [
      mynif: [
        path: "native/mynif",
        mode: (if Mix.env == :prod, do: :release, else: :debug),
      ]
    ]
  end

end
#[macro_use] extern crate rustler;
#[macro_use] extern crate rustler_codegen;
#[macro_use] extern crate lazy_static;

use rustler::{NifEnv, NifTerm, NifResult, NifEncoder};

mod atoms {
    rustler_atoms! {
        atom ok;
        //atom error;
        //atom __true__ = "true";
        //atom __false__ = "false";
    }
}

rustler_export_nifs! {
    "Elixir.Testing.MyNif",
    [("add", 2, add)],
    None
}

fn add<'a>(env: NifEnv<'a>, args: &[NifTerm<'a>]) -> NifResult<NifTerm<'a>> {
    let num1: i64 = args[0].decode()?;
    let num2: i64 = args[1].decode()?;

    Ok((atoms::ok(), num1 + num2).encode(env))
}

native/mynif/src/lib.rs

defmodule Testing.MyNif do
  use Rustler, otp_app: :testing, crate: "mynif"

  def add(_, _), do: err()

  def err(), do: raise "NIF not loaded"
end

lib/testing/mynif.rs

$ iex -S mix
Erlang/OTP 20 [erts-9.0] [source] [64-bit] [smp:4:4] [ds:4:4:10] 
[async-threads:10] [kernel-poll:false]

Interactive Elixir (1.4.5) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> Testing.MyNif.add(1, 1)
{:ok, 2}
iex(2)>

Similar projects

  • Neon for Node.js
  • Helix for Ruby
  • snaek for Python

Thanks!

  • Jason Orendorff
  • Sonny Scroggin
  • ...all small contributions
  • Your name here!

hansihe.com

Q&A