A Rubyist's Walk Along the C-side (Part 1): Hello World!

A Rubyist's Walk Along the C-side (Part 1): Hello World!

This is an article in a multi-part series called “A Rubyist’s Walk Along the C-side”

In this article, we’ll explore how you can set up and build your very first Ruby C extension. At the end of this article, you will be able to write the following Ruby script, in C!

puts "Hello world!"

This probably doesn’t look very exciting to you, but there’s quite a bit of boilerplate involved before we can actually write this simple script. Let’s get started!

Prerequisites 

This article expects that you are familiar with Ruby, and have a basic understanding of C.

You should also have make and gcc (or clang for macOS) installed. For systems with the APT package manager, you can run the following command.

$ sudo apt install -y make gcc

And, of course, you should have a recent Ruby version (e.g. 2.7 or 3.0) installed.

Why do we write C extensions? 

It’s no secret that Ruby is not very fast. Ruby’s C APIs are a bridge between the Ruby world and C world. That way, you can write high-performing C code while still being able to do everything you could in Ruby. Examples of gems that require high performance include pumaliquid-c, and bcrypt.

Another reason to write a C extension is when you need to call C libraries. Example gems are nokogiri and mysql2. You could also use ffi to call C functions from Ruby, but I won’t be covering that in this guide.

Should I write my gem as a C extension? 

If you’re asking yourself this question, chances are the answer is “no”. C extensions are 10x harder to write, maintain, and understand than regular Ruby code. In a C extension, there are so many more issues you have to consider, such as memory management and type safety, that you usually don’t think about when writing Ruby code. Additionally, the Ruby C API is largely undocumented, making it difficult to use and hard to onboard new developers or contributors.

Writing our “Hello world!” script 

You can find the accompanying source code in the GitHub repository peterzhu2118/ruby-c-ext-code

Let’s start writing our C program. Let’s call our program my_c_ext. Create and open the file ext/my_c_ext.c where we will write the code.

The resulting code will look like the following. I’ll explain it line-by-line below.

#include <ruby.h>

void Init_my_c_ext(void)
{
    ID id_puts = rb_intern("puts");

    VALUE hello_world_str = rb_str_new_cstr("Hello world!");
    rb_funcall(rb_mKernel, id_puts, 1, hello_world_str);
}

We start by including the header file for Ruby.

#include <ruby.h>

We can then define the function that will be called when we load this C extension. It must be called Init_<name of file> (Ruby will call this function when we require this C extension) and accept no arguments and returns void.

void Init_my_c_ext(void)

We then call rb_intern, which converts the C string "puts" to the corresponding Ruby symbol :puts. The data type we use here is ID, which is the type in Ruby used for symbols (it’s a type definition for an unsigned long).

ID id_puts = rb_intern("puts");

We then create a new Ruby string out of a C string. Note the type VALUE used here. You’ll be using this type throughout your code as it’s the type used to represent Ruby objects. Just like the type ID, the underlying data type is also an unsigned long.

VALUE hello_world_str = rb_str_new_cstr("Hello world!");

This is where all the magic happens. rb_funcall works like Object.send, you pass in an object instance in the first argument, the symbol for the method you want to call, the number of arguments, and a list of arguments. In our case, the object is the module Kernel (Ruby has builtin variables for various classes). We pass in the symbol :puts that we created a few lines above as the method name. We have just a single argument, and it’s the Ruby string "Hello world!".

rb_funcall(rb_mKernel, id_puts, 1, hello_world_str);

Compilation 

Now that we’ve written our C extension, let’s look at how we can compile it. First, we’ll create a file ext/extconf.rb that helps us generate the Makefile that we need.

require "mkmf"

create_makefile "my_c_ext"

This will require the mkmf (make Makefile) library built into Ruby that will generate the Makefile we need to correctly build and link our C extension.

$ ruby ext/extconf.rb
creating Makefile

$ ls ext
extconf.rb  Makefile  my_c_ext.c

We see that after running this script we get a Makefile generated. We can now use this Makefile to build our C extension.

$ cd ext

$ make
compiling my_c_ext.c
linking shared-object my_c_ext.so

$ ls
extconf.rb  Makefile  my_c_ext.c  my_c_ext.o  my_c_ext.so

Using our C extension 

In order to use our C extension, we need to create a Ruby script. Let’s create my_c_ext.rb and place the following contents in it.

require_relative "ext/my_c_ext"

You might not know this, require and require_relative can not only load other Ruby files, but can also load shared objects (in our case, it will load ext/my_c_ext.so)!

$ ruby my_c_ext.rb
Hello world!

We can run this script, and we see the expected output!

Summary 

In this article, we looked at why Ruby supports C extensions, and how to get started on building your very first C extension. In the next article, we’ll look at how to define methods using the C API.

posted @   unicornsir  阅读(38)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
点击右上角即可分享
微信分享提示