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