A Rubyist's Walk Along the C-side (Part 6): Classes & Object Oriented Programming
This is an article in a multi-part series called “A Rubyist’s Walk Along the C-side”
In the previous article, we saw ways to read and write to various types of variables through the C API. In this article, we’ll look at how to define classes and modules and create instances of classes in the Ruby C API.
Defining classes
Defining top-level classes
To define a top-level class, we can use the rb_define_class
function. It will either return the existing class if one with the same name is already defined, or create one if it does not exist. However, if a class with the same name is already defined, it will compare the superclass of that class to the value of the super
argument. If the two does not match, it will raise a TypeError: superclass mismatch
exception. rb_define_class_under
accepts two arguments and returns the newly created class (or the existing one if it exists):
name
: C string of the class name.super
: The superclass of the class (must be a class and notnil
). All classes have a superclass, we can use therb_cObject
C constant to refer to theObject
class.
// Function prototype for rb_define_class
VALUE rb_define_class(const char *name, VALUE super);
// Creating class Foo with superclass Object
VALUE cFoo = rb_define_class("Foo", rb_cObject);
Defining namespaced classes
To define a namespaced class, we can use the rb_define_class_under
function. It works very similarly to the rb_define_class
discussed above. It will either return the existing class if one is already defined or create one otherwise. It will also raise a TypeError: superclass mismatch
exception if the existing class’s superclass does not match the super
argument passed in. rb_define_class_under
accepts three arguments and returns the newly created class (or the existing one if it exists):
outer
: The outer class or module to define this class in.name
: C string of the class name.super
: The superclass of the class (must be a class and notnil
).
// Function prototype for rb_define_class_under
VALUE rb_define_class_under(VALUE outer, const char *name, VALUE super);
// Creating classes Foo and Foo::Bar
VALUE cFoo = rb_define_class("Foo", rb_cObject);
VALUE cFooBar = rb_define_class_under(cFoo, "Bar", rb_cObject);
Defining modules
Defining modules is very similar to classes, just without the last super
argument (since modules don’t have superclasses).
Defining top-level modules
To define a top-level module, we can use the rb_define_module
function. Once again, just like rb_define_class
, it will return the existing module if one is already defined, or create one otherwise. rb_define_module
accepts one argument and returns the newly created module (or the existing one if it exists):
name
: C string of the module name.
// Function prototype for rb_define_module
VALUE rb_define_module(const char *name);
// Creating module Foo
VALUE mFoo = rb_define_module("Foo");
Defining namespaced modules
To define a namespaced module, we can use the rb_define_module_under
function. At this point, you probably know what I’m going to say next, but I’ll say it again. Just like all the class/module defining functions we’ve seen so far, it will return the existing module if one is already defined, or create one otherwise. rb_define_module_under
accepts two arguments and returns the newly created module (or the existing one if it exists):
outer
: The outer class or module to define this class in.name
: C string of the module name.
// Function prototype for rb_define_module_under
VALUE rb_define_module_under(VALUE outer, const char *name);
// Creating modules Foo and Foo::Bar
VALUE mFoo = rb_define_module("Foo");
VALUE mFooBar = rb_define_module_under(mFoo, "Bar");
Including modules in classes
To include a module in another class/module, we can use the rb_include_module
function. This function accepts two arguments and does not return anything:
klass
: The class or module to include the module into.module
: The module to include intoklass
.
// Function prototype for rb_include_module
void rb_include_module(VALUE klass, VALUE module);
// Include module mFoo into class cBar
rb_include_module(cBar, mFoo);
Extending modules to classes
To extend an object with a module, we can use the rb_extend_object
function. This function accepts two arguments and does not return anything:
obj
: The object to extend the module into.module
: The module to extend intoobj
.
// Function prototype for rb_extend_object
void rb_extend_object(VALUE obj, VALUE module);
// Extend module mFoo into class cBar
rb_extend_object(cBar, mFoo);
Since extend is just including into the singleton class, we can read the implementation of rb_extend_object
and understand what it’s doing!
Creating objects
To create an instance of a class, we can use the rb_class_new_instance
and rb_class_new_instance_kw
functions. The only difference between these two functions is that rb_class_new_instance_kw
has one extra parameter at the end that allows for keyword arguments. rb_class_new_instance
accepts three arguments, rb_class_new_instance_kw
accepts four arguments, and both return the newly created object:
argc
: The number of arguments.argv
: Pointer to the list of arguments.klass
: The class to create an instance of.kw_splat
(only forrb_class_new_instance_kw
): Whether to use the last argument ofargv
as keyword arguments. UseRB_PASS_KEYWORDS
to use the last argument ofargv
as keyword arguments, andRB_NO_KEYWORDS
otherwise. Seerb_funcall_kw
for more details.
// Function prototypes
VALUE rb_class_new_instance(int argc, const VALUE *argv,
VALUE klass);
VALUE rb_class_new_instance_kw(int argc, const VALUE *argv,
VALUE klass, int kw_splat);
// Create a new instance of my_class with
// 3 arguments a, b, c
VALUE args[3] = { a, b, c };
VALUE obj = rb_class_new_instance(3, args, my_class);
Conclusion
In this article, we looked at a way to define classes and modules, including and extending modules, and creating instances of classes. In the next article, we’ll look at how to use and define TypedData objects.