written on October 18, 2012
I was very happy to see Rust 0.4 being released yesterday. It's the first language in a really long time that genuinely interests me. It might be the next language besides C, C# and Python that I would really enjoy. The language design strikes such an exciting balance between doing new things and staying familiar enough to feel easy to pick up.
One of the exciting things of Rust is how it learns from other languages before in an experimental way. The language is designed and overthrown as it's being used and seeing the process unfold is very reminiscent of how Python's early days must have looked like.
One of the surprising features of Rust is how it deals with statement termination and it has not been without criticism. Personally: I am absolutely in love with how it does it but I have seen some people being very opposed to it. So here is me explaining why I think it's the best thing since sliced bread.
Before we can get to that, we need to do a detour into the dynamic language camp, mostly into the Ruby and Python camps. The languages seem familiar on the surface but they are actually very far apart. In fact, the languages could not be different in many ways. One of the most striking differences is how Ruby and Python deal with methods and statements versus expressions.
In Python everything is an object and that includes functions. In fact calling it a function would be wrong, Python programmers like to call them callables. The reason for that is that there are a lot of things you can call but they behave differently in their lowlevel implementations. That's something you will not notice as a Python novice but it certainly shows up at times, especially when it comes to changing the implementation and exposing unintended side effects.
For instance str()
in Python used to be a function and was later
converted into a type. dir()
in Python is a builtin function whereas
quit()
is an instance of a quitter type. cgi.escape()
is a
“function”. On the surface they all work exactly the same, but really the
only thing they have in common is that you can call them.
In Ruby there are no functions, instead there are objects that have
methods. The idea is that instead of invoking a method on an object you
send a message to an object, at least that is the original design. Not
having functions has some profound implications. The most obvious one is
that without first class functions, functional programming works
differently. Ruby developers will generally tell you that functional
programming works better in Ruby than it does in Python and will point out
that blocks are preferable over anonymous functions. What are blocks?
They are basically syntactic sugar to create an object (called a Proc
)
with a method called call
that can be invoked. This proc is then
passed to a function in a special parameter.
Let's talk a bit more practice here. Given a list of four numbers, here is how you would calculate the power of two for each item.
First in Python:
>>> def power_it(x):
... return x ** 2
...
>>> map(power_it, [1, 2, 3, 4])
[1, 4, 9, 16]
Then in Ruby:
>> [1, 2, 3, 4].map { |x| x ** 2 }
=> [1, 4, 9, 16]
The Python version is obviously not the way you would write that in
Python. Making a function like this is not very useful. Instead what you
do in Python is either using a list comprehension (avoiding the problem)
or to use the lambda
keyword that allows you to create an unnamed
function that has a single expression as body. Let's ignore that for a
moment though. The example above shows something very interesting: it
shows how different the languages treat expressions.
In Python there are statements and expressions. The syntax allows for statements to contain other statements and expressions to contain other expressions. If an expression is used in the spot where a statement is expected it's wrapped in what the grammar calls an “expression statement”. The purpose of the expression statement is to throw away the resulting value of the expression. This is obvious in both the syntax representation as well as the bytecode.
Take this very benign example:
foo()
On a syntax level this is the representation:
Module(body=[Expr(value=Call(func=Name(id='foo', ctx=Load()),
args=[], keywords=[],
starargs=None, kwargs=None))])
The call appears on module level, a module has multiple statements in the
body. In this case it calls the name “foo” (which is loaded) with no
arguments or keyword arguments of any sort. Since it's an expression it
is wrapped in an Expr
node. This allows an expression to be used
there and also then tells the code generator to throw away the result.
This would be the bytecode for it:
2 0 LOAD_GLOBAL 0 (foo)
3 CALL_FUNCTION 0
6 POP_TOP
It tells the interpreter: load the value for the global variable “foo”, then call it without arguments, then throw away the return value.
This is very different from Ruby. In Ruby many statements are implemented
in a way that they are either expressions or at least behave in such a
way. The language also very often does not throw away values like Python
does. This is helpful because for instance methods return the value of
the last expression when they return. Ruby follows that design mantra to
ridiculous ways. For instance the following example defines a variable
called foo
and an empty class Bar
where foo
contains the last
expression within Bar