Writing Ruby extensions in Rust: Part 1

Posted on November 17, 2018

Ruby is not known for its performance, and rewriting the whole application in a faster language is rarely an option in the real world. What to do if there are few bottlenecks in otherwise reasonably fast code? Native extensions to the rescue! Usually native extensions are written in C and there are some good tutorials, e.g. this one. But C code like below looks … scary?

Is it possible to avoid at least some of the boilerplate and maybe use a slighty more modern language than C? One possibility is to use Rust and helix. Let’s create simple gem from scratch. I assume that Ruby and bundler are already installed (if you prefer system Ruby you will need ruby-dev package (Ubuntu) or similar for your system). We also need to install Rust:

curl https://sh.rustup.rs -sSf | sh

Let’s start from creating gem template:

I chose name helix_csv for a reason. Later I plan to convert this gem to fast CSV parser. After fixing helix_csv.gemspec TODOs to make bundler happy and changing module HelixCsv to class HelixCSV in lib , we need to add helix dependency:

and then run bundle install --path=vendor/bundle to install required gems. Next we need to replace Rakefile content with something like this:

This will add tasks necessary to compile Rust code. Let’s add Rust template:

cargo for Rust is what bundler is for Ruby and much more. This command will create Cargo.toml (which is Rust’s equivalent of Gemfile ) and Rust code template in src/lib.rs . We need to add [lib] section to Cargo.toml since we need dynamic system library:

and helix to the [dependencies] section:

Before we can start writing Rust code we need to create a glue between Ruby and Rust in lib/helix_csv.rb by adding these two lines to the top of that file:

Now we can finally write gem code. Let’s replace content of src/lib.rs with:

To build extension we can either run rake build or just rake since default target is build anyway:

Bundler has already created bin/console which will load our gem, so we can do some testing:

Not terribly interesting gem, but it works! We can’t package the gem yet as gem install needs to build native extension and we need to instruct gem how to do it. It’s not very complicated though, we only need to add spec.extensions = %w[extconf.rb] to our helix_csv.gemspec and script extconf.rb to the root of our gem. The purpose of this script is to create Makefile :

Since gem packager is using git to retrieve file list as a bare minimum we need to add files to the git cache (with appropriate .gitignore ):

Finaly we can package our gem:

Done! We can now publish the gem, or simply create new directory, add helix_csv-0.1.0.gem to vendor/cache , add something like this to Gemfile :

then run:

In the part 2 I’m going to implement CSV parser based on Rust CSV crate and do some benchmarks.