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 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 cargo init
Create Cargo.toml.
cargo init --lib Cargo.toml
Add the following two lines to Cargo.toml.
[lib] crate-type = ["cdylib"] 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] 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) } } 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 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.
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 Try rake clean.
Then tree or exa -T
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 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 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" 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 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.
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.
Now let's run the gem.
bundle exec bin/consle
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 Installation
rake install
Check if Gem works without Bundler.




Top comments (1)
Very straightforward, thanks!