Writing C within Ruby

This started off as an internal thread as to why C++ just downright sucks. There’s been a whole lot of hoopla around the security vulnerabilities while writing C++ code, specifically to do with delete and delete[]. I frankly think C++ for a large scale project is a big mistake.

Lately, we’ve been edging over to writing all our C code within the Ruby interpreter. There are a number of reasons:

  • Within the Ruby interpreter, the language-stack frame (the interpreter uses alloca a lot) and the C stack frame are merged into one.
  • In other words, the mark-and-sweep garbage collection walks the C stack frame and the language stack frame in a unified way to mark objects that are currently in use.
  • The rb_hash_*, rb_str_* and the rb_ary_* API are as powerful, if not more as stl::map, stl::string and stl::vector and you get free regex support for the String objects that you create/manage.
  • With rb_yield and rb_raise, you get iterator support, as well as exception handling without worrying about dangling pointers and unfree’d memory during stack unwinding.
  • And yes, you get to ‘include Enumerable‘ (rb_include_module) into your class as long as you implement the each method in your Class, so that the caller can do obj.each to iterate over your internals. This, BTW, is essentially the Visitor pattern.
  • It forces you to think about what you build as an Object giving you full OOP within C with no worries about vtables and the goriness of virtual functions. Just for the record, I hate when you override a protected method, have a typo in the method signature (missing const, for example) and debug for a few hours as to why your overridden method is never invoked.
  • The code, without the all pervasive checks for errno’s and return values, actually makes it readable by anyone. All you do it invoke rb_raise to raise an exception when there’s an error condition and rb_rescue to catch it.
  • Whatever you write is automatically scriptable, without having to build an explicit API. It’s amazing how many people spend time on taking an existing implementation to build an API on it.
  • The previous point means, you can build runit
    test cases to validate your code to make sure it works as expected.

If you are embedding the interpreter in your C code, just make sure you call ruby_init() in main() so that garbage collector doesn’t end up with negative offsets. More on this in a later blog.

Bookmark and Share