Posted ; Last Updated
So Lua and Ruby are two rather similar languages in many aspects. There's a lot of articles explaining what makes them so similar and many of the more superficial differences.
Today I'd like to point out some reasons that have shaped my opinion on the two languages.
I will risk sounding like a hater, but I'd like to point out that I don't particularly dislike Ruby. There's many positive things about it, but since I do prefer Lua, I just chose to point out some of its problems today.
It'd be easy to just say "Lua fast, Lua good" and be done with it. In reality though, things aren't always that easy.
The thing about Lua is, it's very small. Not only does this mean that it fits almost everywhere, but also that it loads very fast
> time ruby -e 'puts "hello"' hello real 0m0.059s user 0m0.054s sys 0m0.005s > time lua -e 'print "hello"' hello real 0m0.003s user 0m0.003s sys 0m0.001s
This only makes a difference when you're calling the executable many times in a row, but the difference does add up over time.
Functions vs. Methods
The next thing to consider is that, in Lua, functions are stored in variables, and are therefore easy to look up.
Ruby, on the other hand, has to look up methods for everything. For short methods (let's say, adding two numbers) this can mean a significant overhead.
> ruby -e 'puts self.class.ancestors' Object Kernel BasicObject
Even the main object every ruby script runs in inherits from 3 ancestors.
It should be noted that Lua isn't immune to this problem. When writing object-oriented code, this also ends up happening. The difference is that in Lua the programmer must do this explicitly, while in Ruby it happens all the time.
Strings / Symbols
This may surprise some people, but Lua actually has a huge disadvantage to Ruby in terms of speed: It has no Strings in the way Ruby has them. All strings in Lua are automatically interned. Ruby has interned strings as well; it calls them Symbols.
This has its upsides and, overall, was probably a smart decision, but it also means reading in a long text file takes much longer in Lua. Imagine calling
to_sym on every line in Ruby.
If I was asked which of the two languages had the powerful implementation of variadic functions, I'd 100% say Ruby. You can mix and match many kinds of syntactic constructs that capture additional arguments into both arrays and hashes.
But, with great strength comes... not so great performance. Let's consider the following piece of code:
def foo(bar, *rest) do_something(bar) do_something(*rest) end
Every time the foo method is called, ruby needs to instantiate a new array
rest and collect the excess arguments into it. This ends up happening a lot and makes variadic methods very unsuited for code that needs to perform well.
Consider the equivalent code in Lua:
function foo(bar, ...) do_something(bar) do_something(...) end
Here, there's no array involved. The function can just leave bar on the stack, call
do_something telling it it has 1 argument, then pop bar from the stack and call
do_something again, telling it how many items are left on the stack.
This means writing functions like this is way more viable even when your code needs to run as fast as possible.
One difference that probably makes a way larger difference than most people would assume is the difference in mentality between the two communities.
When asked why Ruby is so great, the general response from its community will be something along the lines of "Because it's fun to write!". Many Ruby examples out there seem to throw performance out the window before even starting. This isn't necessarily a bad thing, but it ultimately leads to people writing libraries that run way slower than they could.
My experience with the Lua community has been vastly different. A simple google search brings up way more relevant and detailed information on how to improve performance in Lua than in Ruby, even though the latter has a much larger community that also seems to write way more about it.
Lua is already very fast on its own. LuaJIT though, that's a completely different level. There have been examples where JITed Lua code can even run faster than equivalent C code, because the compiler sometimes has more awareness of the code it's optimizing (After all, it keeps track of the code as it's being executed)
Ruby has made a huge step in the right direction with its own JIT Compiler, but that's still nowhere near the performance improvements of LuaJIT when compared to PUC Lua, the "reference" Lua implementation.
Leaving performance aside now, there's another reason why I consider simplicity a very positive feature of Lua: It's not only easy to learn, but also easy to master.
This is probably one of the things I hate the most about Ruby. No matter how hard you try, you will never get rid of global state. At the very least you will have some modules and classes polluting the global environment, and there is no way to load a library into a contained space.
This means, for example, that it's not possible to load two different versions of one library without having to go through its source code and renaming things everywhere.
require 'some_class' foo = SomeClass.new # Where did this come from?!
In Lua, on the other hand, you can just rename the file and load it into another local. How things are called internally doesn't matter to the host program.
local someclass = require 'someclass' local foo = someclass.new()
Ruby tries to be as comfortable as possible for the developer. I often find myself wishing it didn't
Consider the following example:
require 'missile_cruiser' MissileCruiser.new.last_fired
What happens there? One would assume, the variable
last_fired will return the last missile that was fired. Since we didn't fire one, it would be reasonable for this to be
But wait, what if it's a method?
Maybe it will raise an error because we didn't fire any missile yet?
Even worse, maybe some developer thought if we wanted to know about the last missile, we probably wanted to fire one, so the method just fires a missile and returns that one?
This kind of thing happens often in Ruby. The lines between what is a value and what is code that gets executed are blurred.
Try the following code in
puts 1/0 puts 0/0 puts 1.0/0 puts 0.0/0
Things like that make a language (and, by extension, programs written in it) harder to understand at first glance, specially to newcomers. No language will ever be 100% consistent, but these inconsistencies should still be reduced whenever possible.