Disappointments in Ruby Land
My last article, SQLite3 Bindings for Ruby, mentioned some pure-Ruby bindings that I wrote for the SQLite3 database engine. Pure-Ruby, as in, “needing no compilation.” They use the Ruby/DL library that comes with Ruby, which allows Ruby programs to call directly into arbitrary C libraries. (See the SQLite/Ruby project page if you’d like to try it out.)
Alas, some things are too good to be true.
The Ruby/DL stuff is cool—don’t get me wrong. I could never have written that library, not in a million years. It’s got more magic than Gandalf, and more glitz than Zsa Zsa Gabor.
That said, it also has more trap doors than an Indiana Jones movie.
The SQLite3 bindings almost work. For me, they run very well, unless I run them inside WEBrick, in which case I get deadlocks and segfaults. (I can’t really pin that on Ruby/DL, though, since I have nothing else to test it with.)
However, the SQLite3 bindings don’t work on (for instance) the Mac. Why? Because sqlite3 uses 64-bit integers to represent row-ids, and thus the “last_insert_rowid” method returns an 8-byte integer value.
Can Ruby/DL handle 8-byte integer values?
No, sir.
(I should say, here, that I have tried various work arounds. Having the method return a char[8]
didn’t work, because the syntax was unrecognized. Returning a double
and then doing some pack/unpack magic didn’t work either. Feel free to experiment with it yourself—maybe you’ll find a workaround.)
So I just defined that method (and the few others that use 64-bit ints) as a traditional 32-bit integer, thus truncating the most-significant bytes for me. This worked fine, on my machine.
But on a machine with the endianness reversed, the least-significant-bytes get thrown away. Which means that if the row-id is a 1 (0×0000000000000001), a zero gets returned. Just freaking lovely.
Also, Ruby/DL has a compile-time limit of 10 callbacks (although you can change that number yourself if you compile Ruby manually). This means that if you have multiple libraries that all use Ruby/DL, you will have a very high likelihood of problems if many of those libraries try to register callback methods.
Lastly, Ruby/DL is very susceptible to memory errors. You can declare a function as returning a “const char*”, and Ruby/DL will make sure the result is never freed by the Ruby interpreter. However, declare any other kind of “const” pointer return value, and the const
is ignored, meaning that if you don’t manually remove the free handler from the pointer, you’ll get random crashes and segfaults. Not exactly consistent, and I hope you’d agree.
All in all, this experience has demonstrated to me, more than anything, that Ruby/DL is not usable for anything more than a simple DLL call or two. It’s really just a toy, and cannot be relied on for any serious tasks.
Which is too bad. The pure Ruby bindings to SQLite3 would have been a huge boon.