- Author
-
- Name
- Jason Stiebs
- Social Media
- View Twitter Profile

This post is about using Rust with Elixir and how easily it can be done! If you want to deploy your Phoenix LiveView app right now, then check out how to get started. You could be up and running in minutes.
Problem
We need to perform a CPU intensive or system level programming task and there are just no good solutions in hex.pm, in this example let’s pretend there are no good ways to do image processing with Elixir.
As is often the case, there IS a high quality Rust library called image that claims to be just the solution! But shoot, our entire application is written in Elixir already, and we really don’t know how to use Rust that well.
How can Elixir turn to Rust code for high-performance operations?
Solution
Enter rustler, this library is designed to make using Rust and its package ecosystem trivial. Let’s dive in!
Following the getting started guide, first add rustler to our mix.exs
file:
Once we run mix deps.get
use the built-in mix task to generate our empty rust project:
This is the name of the Elixir module the NIF module will be registered to.
Module name > MyApp.RustImage
This is the name used for the generated Rust crate. The default is most likely fine.
Library name (myapp_rustimage) > rust_image
* creating native/rust_image/.cargo/config.toml
* creating native/rust_image/README.md
* creating native/rust_image/Cargo.toml
* creating native/rust_image/src/lib.rs
* creating native/rust_image/.gitignore
Ready to go! See /Users/me/projects/my_app/native/rust_image/README.md for further instructions.
You should go open up that README.md
, but I’ll save you the hassle, we need to make an Elixir module in lib/my_app/rust_image.ex
that has the following contents:
defmodule MyApp.RustImage do
use Rustler, otp_app: :my_app, crate: "rust_image"
# When your NIF is loaded, it will override this function.
def add(_a, _b), do: :erlang.nif_error(:nif_not_loaded)
end
And from then on out we’re ready to do some Rust. The default generator gives us an add/2
function implemented in native/rust_image/src/lib.rs
let’s take a look
#[rustler::nif]
fn add(a: i64, b: i64) -> i64 {
a + b
}
rustler::init!("Elixir.MyApp.RustImage", [add]);
What Is a NIF?
Native Implemented Functions are the BEAM’s method of allowing processes to directly call native functions. They normally have a ton of boilerplate and you need to be serious about cleaning up your memory, handling errors and being safe. Luckily that’s Rust’s entire thing! For example here is the Erlang NIF tutorial. We didn’t need to do any of that!
Our hyper optimized code will add two integers of size i64 and return the result. Note the Rustler specific parts here:
#[rustler::nif]
is a macro that tells Rustler to expose this function as a NIF.rustler::init!("Elixir.MyApp.RustImage", [add]);
This initializes the Erlang NIF runtime so that the beam can put theadd/2
function on theElixir.MyApp.RustImage
module and replace the stub we left.
This is amazing. To see if this works, lets fire up iex -S mix
iex(1)> MyApp.RustImage.add(100, 20)
120
If everything worked the first time, you should have seen cargo building the app in release mode and succeeding before opening the iex term. If you didn’t already have Rust installed it would have shown an error, you can install Rust the usual way.
Rustler is even smart and will recompile automatically, leave iex open and change our lib.rs
#[rustler::nif]
fn add(a: i64, b: i64) -> i64 {
a + b + 1
}
Save and then open that running iex session again:
iex(2)> r(MyApp.RustImage)
... truncated output of cargo doing it's thing an maybe some beam warnings
{:reloaded, [MyApp.RustImage]}
iex(3)> MyApp.RustImage.add(1,1)
3
Incredible! We get the same workflow and nice bits of working with Elixir