Making Ruby Gem with Rust
Tags: ruby, rust
Published: 2020-03-20 13:55:43

---
title: Making Ruby Gem with Rust 
published: true
tags: ruby, 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!](https://www.reddit.com/r/ruby/comments/fhu07l/released_a_string_metricsdistance_gem_written_in/)

* [Github - StrMetrics](https://github.com/anirbanmu/str_metrics)

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`.

```sh
bundle gem fib -t rspec
cd fib
```

`fib` stands for Fibonacci.

## Gemspec

Edit `fib.gemspec`. I like the simple one.

```ruby
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

```

## cargo init

Create `Cargo.toml`.

```sh
cargo init --lib
```

## Cargo.toml

Add the following two lines to `Cargo.toml`.

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

It should look like this.

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

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

[dependencies]
```

`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.

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

```

# Rakefile

Add the `rust_build`task to the Rakefile.

```ruby
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
```

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
```

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/4ef211dc-9a32-30b3-c4ec-7a923583e2ac.png)

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

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

task :clean => :rust_clean
```

Try `rake clean`.

Then `tree` or `exa -T`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/144608/d76aa0b0-e327-d450-aa28-59df1e0831ce.png)

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

## FFI

I use [Foreign function interface (FFI)](https://en.wikipedia.org/wiki/Foreign_function_interface) to call Rust from Ruby.
Ruby has two FFI libraries. [fiddle](https://github.com/ruby/fiddle) and [ruby-ffi](https://github.com/ffi/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`.

```ruby
module Fib
  module FFI
  end
end
```

![Overview](https://dev-to-uploads.s3.amazonaws.com/i/o70moc9v3a1q3p0zggh3.png)

### Fiddle

[Fiddle](https://github.com/ruby/fiddle) is a standard Ruby library, so you can use it right away.

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

```ruby
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
```

If Fiddle does not work on Windows, check [this](https://github.com/oneclick/rubyinstaller2/wiki/For-gem-developers#-dll-loading).

### Ruby-FFI

[Ruby-FFI](https://github.com/ffi/ffi) is not a Ruby standard library. You need to edit `fib.gemspec`.

```ruby
spec.add_dependency "ffi"
```

`bundle update` to install `ruby-ffi`.

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

```ruby
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
```

## Making Ruby methods

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

```
Fib[3] # => 2
```

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

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

module Fib
  def self.[](n)
    FFI.fib(n)
  end
end
```

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](https://dev-to-uploads.s3.amazonaws.com/i/o70moc9v3a1q3p0zggh3.png)

Now let's run the gem.

`bundle exec bin/consle`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/83yl1obqrqarjl76kr6t.png)

It Works well.

## Add a Test file

Open `spec/fib_spec.rb`.

```ruby
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
```

## Installation

`rake install`

Check if Gem works without Bundler. 

## Original article 

* [Rust でつくるかんたん Ruby Gem](https://qiita.com/kojix2/items/69aa17114e794e3ccb26)