DEV Community

Thanabodee Charoenpiriyakij
Thanabodee Charoenpiriyakij

Posted on

Rustler 101: Ferris Say

สร้าง Elixir Project ด้วย mix

$ mix new ferris_ex --module FerrisEx

ใส่ rustler เข้าไปใน mix dependencies

defp deps do
  [
    {:rustler, "~> 0.21.1"}
  ]
end

หลังจากนั้นก็สั่ง mix deps.get เพื่อ fetch dependencies

ต่อมา generate rust project ผ่าน rustler ก็จะมี prompt ให้ใส่ชื่อ module ลงไป ผมใส่ module ชื่อ FerrisEx.Native และชื่อ rust library ก็ตามน้ำมันไป

$ mix rustler.new
==> toml
Compiling 10 files (.ex)
Generated toml app
==> rustler
Compiling 5 files (.ex)
Generated rustler app
==> ferris_ex
This is the name of the Elixir module the NIF module will be registered to.
Module name > FerrisEx.Native
This is the name used for the generated Rust crate. The default is most likely fine.
Library name (ferrisex_native) >
* creating native/ferrisex_native/.cargo/config
* creating native/ferrisex_native/README.md
* creating native/ferrisex_native/Cargo.toml
* creating native/ferrisex_native/src/lib.rs
Ready to go! See /Users/thanabodee/src/github.com/wingyplus/ferris_ex/native/ferrisex_native/README.md for further instructions.

ต่อมาเปิด Cargo.toml ซึ่งอยู่ใน $PWD/native/ferrisex_native/Cargo.toml แล้วทำการใส่ library ชื่อ ferris_says ลงไป

[dependencies]
rustler = "0.21.1"
lazy_static = "1.0"
...
ferris-says = "0.2"

และทำการแก้โค้ดของ lib.rs ให้เป็น function ตามที่เราต้องการ

use ferris_says;
use rustler::{Encoder, Env, Error, Term};
use std::io;
use std::str;

mod atoms {
    rustler::rustler_atoms! {
        atom ok;
    }
} // 1

rustler::rustler_export_nifs! {
    "Elixir.FerrisEx.Native",
    [
        ("say", 1, say)
    ],
    None
} // 2

fn say<'a>(env: Env<'a>, args: &[Term<'a>]) -> Result<Term<'a>, Error> {
    let text: String = args[0].decode()?;

    let mut cursor = io::Cursor::new(Vec::new());
    ferris_says::say(&text.into_bytes(), 24, &mut cursor).unwrap();
    Ok((atoms::ok(), str::from_utf8(&cursor.into_inner()).unwrap()).encode(env))
} // 3

จาก snippet ข้างบน

  1. ประกาศ atom ที่เราจะใช้ใน rust library
  2. ประกาศ function ใน module FerrisEx.Native
  3. implement function

เสร็จแล้วก็สั่ง cargo build เพื่อให้ rust สร้าง dynamic library ขึ้นมา

ต่อมาทำการสร้าง elixir module เพื่อ binding กับ rust library ของเรา

# ./lib/ferris_ex/native.ex
defmodule FerrisEx.Native do
  use Rustler, otp_app: :ferris_ex, crate: :ferrisex_native

  def say(_text), do: :erlang.nif_error(:nif_not_loaded)
end

เพื่อที่จะทำการ binding กับ rust library ของเราได้ต้องใช้ use Rustler และบอกว่าจะผูกกับ crate ชื่ออะไรของเรา และต้องมี function dummy เพื่อกรณีที่ elixir binding nif ไม่ได้

เสร็จแล้วก็ copy dynamic library ที่ได้จาก cargo build มาไว้ที่ priv/native ของ elixir

$ cp ./native/ferrisex_native/target/debug/libferrisex_native.dylib ./priv/native/libferrisex_native.so

หลังจาก copy แล้วก็ทำการเปิด iex ขึ้นมาเพื่อทดสอบ binding ของเรา

iex(1)> {:ok, out} = FerrisEx.Native.say("Hello, I'm Ferris.")
{:ok,
 " ____________________\n< Hello, I'm Ferris. >\n --------------------\n        \\\n         \\\n            _~^~^~_\n        \\) /  o o  \\ (/\n          '_   -   _'\n          / '-----' \\\n"}
iex(2)> IO.puts(out)
 ____________________
< Hello, I'm Ferris. >
 --------------------
        \
         \
            _~^~^~_
        \) /  o o  \ (/
          '_   -   _'
          / '-----' \

:ok

Discussion (0)