Building a Ruby C Extension From Scratch

In this edition of Ruby Magic, we’ll show you how to use code written in C from Ruby. This can be used to optimize performance sensitive parts of your code or to create an interface between a C library and Ruby. This is done by creating extensions that wrap libraries written in C.

There are a lot of mature and performant libraries written in C. Instead of reinventing the wheel by porting them we can also leverage these libraries from Ruby. In this way, we get to code in our favorite language, while using C libraries in areas where Ruby isn’t traditionally strong. At AppSignal, we’ve used this approach in developing the rdkafka gem.

So let’s see how one can approach this. If you want to follow along and experiment yourself, check out the example code. To start off, let’s take this piece of Ruby code with a string, a number and a boolean (you’ll C why, pun intended) and port it to a C library:

module CFromRubyExample
class Helpers
def self.string(value)
"String: '#{value}'"

def self.number(value)
value + 1

def self.boolean(value)

In order, the methods shown concatenate a string, increase a number by one and return the opposite of a boolean, respectively.

Our Library Ported to C

# include <stdlib.h>
# include <stdio.h>

char* string_from_library(char* value) {
char* out = (char*)malloc(256 * sizeof(char));
sprintf(out, "String: '%s'", value);
return out;

int number_from_library(int value) {
return value + 1;

int boolean_from_library(int value) {
if (value == 0) {
return 1;
} else {
return 0;

As you can see, you have to jump through some hoops to do simple string formatting. To concatenate a string, we first have to allocate a buffer. With this done, the sprintf function can then write the formatted result to it. Finally, we can return the buffer.

With the code above, we already introduced a possible crash or security issue. If the incoming string is longer than 245 bytes, the dreaded buffer overflow will occur. You should definitely be careful when writing C, it’s easy to shoot yourself in the foot.

Next up is a header file:

char* string_from_library(char*);
int number_from_library(int);
int boolean_from_library(int);

This file describes the public API of our C library. Other programs use it to know which functions in the library can be called.

The 2018 Way: Use the ffi Gem

module CFromRubyExample
class Helpers
extend FFI::Library

ffi_lib File.join(File.dirname(__FILE__), "../../ext/")

attach_function :string, [:string], :string
attach_function :number, [:int], :int
attach_function :boolean, [:int], :int

For the purpose of this article, we’re also going to explain how to wrap the C code with a C extension. This will give us much more insight into how it all works under the hood in Ruby.

Wrapping our Library in a C Extension do |spec| = "c_from_ruby_example"
# ...
spec.require_paths = ["lib", "ext"]

This informs Rubygems that there is a native extension that needs to be built. It will look for a file called extconf.rb or a Rakefile. In this case, we added extconf.rb:

require "mkmf"

create_makefile "extension"

We require mkmf, which stands for “Make Makefile”. It’s a set of helpers included with Ruby that eliminates the finicky part of getting a C build set up. We call create_makefile and set a name for the extension. This creates a Makefile which contains all the configuration and commands to build the C code.

Next, we need to write some C code to connect the library to Ruby. We’ll create some functions that convert C types such as char* to Ruby types such as String. Then we’ll create a Ruby class with C code.

First off, we include some header files from Ruby. These will import the functions we need to do type conversion. We also include the library.h header file that we created earlier so that we can call our library.

#include "ruby/ruby.h"
#include "ruby/encoding.h"
#include "library.h"

We then create a function to wrap each function in our library. This is the one for string:

static VALUE string(VALUE self, VALUE value) {
Check_Type(value, T_STRING);

char* pointer_in = RSTRING_PTR(value);
char* pointer_out = string_from_library(pointer_in);
return rb_str_new2(pointer_out);

We first check if the Ruby value coming in is a string, since processing a non-string value might cause all sorts of bugs. We then convert the Ruby String to a char* with the RSTRING_PTR helper macro that Ruby provides. We can now call our C library. To convert the returned char*, we use the includes rb_str_new2 function. We’ll add similar wrapping functions for number and boolean.

For numbers, we do something similar using the NUM2INT and INT2NUM helpers:

static VALUE number(VALUE self, VALUE value) {
Check_Type(value, T_FIXNUM);

int number_in = NUM2INT(value);
int number_out = number_from_library(number_in);
return INT2NUM(number_out);

The boolean version is also similar. Note that C doesn’t actually have a boolean type. The convention is to instead use 0 and 1.

static VALUE boolean(VALUE self, VALUE value) {
int boolean_in = RTEST(value);
int boolean_out = boolean_from_library(boolean_in);
if (boolean_out == 1) {
return Qtrue;
} else {
return Qfalse;

Finally, we can wire up everything so that we can call it from Ruby:

void Init_extension(void) {
VALUE CFromRubyExample = rb_define_module("CFromRubyExample");
VALUE NativeHelpers = rb_define_class_under(CFromRubyExample, "NativeHelpers", rb_cObject);

rb_define_singleton_method(NativeHelpers, "string", string, 1);
rb_define_singleton_method(NativeHelpers, "number", number, 1);
rb_define_singleton_method(NativeHelpers, "boolean", boolean, 1);

Yes, you read that right: we can create Ruby modules, classes and methods in C. We set up our class here. We then add Ruby methods to the class. We have to provide the name of the Ruby method, the name of the C wrapper function that will be called and indicate the number of arguments.

After all that work, we can finally call our C code:

CFromRubyExample::NativeHelpers.string("a string")


Originally published at on October 30, 2018.

Error tracking and performance insights for Ruby and Elixir without the silly per-host pricing. From Amsterdam with love.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store