DEV Community

kojix2
kojix2

Posted on • Updated on

Making Ruby Gem with Rust

Writing Ruby extensions in C is a popular way to speed up Ruby.
A few days ago on Reddit, I read a post titled Released a string metrics/distance gem written in Rust!

It seems that you can make Ruby Gems with Rust. I watched the contents of the repository and made a simple gem that calculates the Fibonacci number.

Create a Gem

Create a Gem template with bundler.

bundle gem fib -t rspec
cd fib
Enter fullscreen mode Exit fullscreen mode

fib stands for Fibonacci.

Gemspec

Edit fib.gemspec. I like the simple one.

require_relative 'lib/fib/version'

Gem::Specification.new do |spec|
  spec.name          = "fib"
  spec.version       = Fib::VERSION
  spec.author        = ["kojix2"]
  spec.summary       = "Get the fibonacci number"
  spec.files         = Dir['lib/**/*', 'src/**/*.rs', 'Cargo.toml', 'LICENSE', 'README.md']
  spec.require_paths = ["lib"]
end

Enter fullscreen mode Exit fullscreen mode

cargo init

Create Cargo.toml.

cargo init --lib
Enter fullscreen mode Exit fullscreen mode

Cargo.toml

Add the following two lines to Cargo.toml.

[lib]
crate-type = ["cdylib"]
Enter fullscreen mode Exit fullscreen mode

It should look like this.

[package]
name = "fib"
version = "0.1.0"
authors = ["author <soda_mail_siyo@mail.com>"]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
Enter fullscreen mode Exit fullscreen mode

cdylib is used when compiling a dynamic library to be loaded from another language(Ruby).

You now have a Gem template.

Add Rust code to calculate Fibonacci numbers

Open src/lib.rs and add the following functions.

#[no_mangle]
pub extern fn fib(n: u32) -> u32 {
    if n <= 1 {
        n
    } else {
        fib(n - 1) + fib(n - 2)
    }
}

Enter fullscreen mode Exit fullscreen mode

Rakefile

Add the rust_buildtask to the Rakefile.

task :rust_build do
  `cargo rustc --release`
  `mv -f ./target/release/libfib.so ./lib/fib` # Change here according to your OS.
end

task :build => :rust_build
task :spec => :rust_build
Enter fullscreen mode Exit fullscreen mode

Try rake rust_build to see if it works.

   Compiling fib v0.1.0 (/home/kojix2/Ruby/fib)
    Finished release [optimized] target(s) in 3.50s
Enter fullscreen mode Exit fullscreen mode

Check the directories with the tree command.
You can see that libfib.so is located in the lib/fib directory.

image.png

Add rust_clean in the same way (This is optional).

task :rust_clean do
  `cargo clean`
  `rm -f ./lib/fib/libfib.so` # Change here according to your OS.
end

task :clean => :rust_clean
Enter fullscreen mode Exit fullscreen mode

Try rake clean.

Then tree or exa -T

image.png

You will see that the target directory and libfib.so have been removed.

FFI

I use Foreign function interface (FFI) to call Rust from Ruby.
Ruby has two FFI libraries. fiddle and ruby-ffi. In a simple case like this one, either is fine.

Create the FFI module

Add the Rust functions as a Ruby method to this FFI module.
lib/fib/ffi.rb.

module Fib
  module FFI
  end
end
Enter fullscreen mode Exit fullscreen mode

Overview

Fiddle

Fiddle is a standard Ruby library, so you can use it right away.

Create a new file lib/fib/ffi.rb.

require 'fiddle/import'

module Fib
  module FFI
    extend Fiddle::Importer
    dlload File.expand_path('libfib.so', __dir__)
    # Mac : libfib.dylib, Win : libfib.dll
    extern 'unsigned int fib(unsigned int n)' # like C language
  end
end
Enter fullscreen mode Exit fullscreen mode

If Fiddle does not work on Windows, check this.

Ruby-FFI

Ruby-FFI is not a Ruby standard library. You need to edit fib.gemspec.

spec.add_dependency "ffi"
Enter fullscreen mode Exit fullscreen mode

bundle update to install ruby-ffi.

Create a new file lib/fib/ffi.rb.

require 'ffi'

module Fib
  module FFI
    extend FFI::Library
    lib_name = "libfib.#{::FFI::Platform::LIBSUFFIX}"
    ffi_lib File.expand_path(lib_name, __dir__)
    attach_function :fib, [:uint], :uint
  end
end
Enter fullscreen mode Exit fullscreen mode

Making Ruby methods

This time, I will call the Fibonacci number with the idiom.

Fib[3] # => 2
Enter fullscreen mode Exit fullscreen mode

Open lib/fib.rb and edit it as follows.

require "fib/ffi" # 追加
require "fib/version"

module Fib
  def self.[](n)
    FFI.fib(n)
  end
end
Enter fullscreen mode Exit fullscreen mode

Of course, you can add functions to the Fib module at first hand. However, sometimes you need to do something on the Ruby side before calling functions in other languages like Rust. Therefore, it is safer to create a special module for FFI and call its methods indirectly.

Overview

Now let's run the gem.

bundle exec bin/consle

Alt Text

It Works well.

Add a Test file

Open spec/fib_spec.rb.

RSpec.describe Fib do
  it "has a version number" do
    expect(Fib::VERSION).not_to be nil
  end

  it "calculates fibonatti" do
    expect(Fib[10]).to eq(55)
  end
end
Enter fullscreen mode Exit fullscreen mode

Installation

rake install

Check if Gem works without Bundler.

Original article

Top comments (1)

Collapse
 
rhymes profile image
rhymes

Very straightforward, thanks!