Friday, May 29, 2015

Go, Rust, Python... some random impressions

During a discussion in the Italian Python mailing list, there were some comments about "C-family" languages and how they do compare to Python. As a consequence this post is partly a translation and partly a reworking of the opinions I expressed there. Since it is deprived of much of its context, some parts could not make too much sense; plus I suffer from a severe form of... well I tend to digress. So it might suck rat's ass, but there might be something interest, so bear with me. This post is neither meant to imply any inherent merit in being "similar" to Python nor to criticize any language. As a full disclosure, I love Python and quite like Go. Rust gave me a very good impression, too.

So, Rust syntax shows C influences (even though I feel like the main influence from the C family is actually C++ rather than plain C), but overall the influence of ML is much stronger, especially if we also consider the semantics. Why we ended up comparing Rust with Python is a pretty long story, which started from this post (and evolved here -- in Italian --; thanks Carlos). At the end, other languages were somewhat drawn in the comparison, including Go, C, C++, Java and a C# that is still under shock for having been freed but is alive and kicking nonetheless (my avoidance to make explicit what it is kicking is intentional). Actually, I revisited the thread and we also mentioned OCaml, Haskell, SML (that might have been my fault), Erlang , Elixir, F#, D, Nim (also my fault), Perl, Luc Besson (which is not a programming language, but does awesome movies), Ruby (the programming language), Clojure, Scala, even PHP (oh, well, this blog was supposed to be PG13) and Ceylon (which I did not even know it existed: either I am getting old or people are creating too many languages). Sadly nobody mentioned Lisp or Scheme and surprisingly nobody mentioned Javascript. If your favorite language is not listed here, add a comment. At the end of the day much of the discussion involved Python, Rust, Go, Java and C++, which was to be expected.

Rust and Python

All considered, I feel like Rust is philosophically one of the farthest languages from Python. Again, this is not a bad or a good thing; it is the way things are.

Static vs. Dynamic typing

Python is entirely on the dynamic side of typing. It does not support static typing at all, not even the new Python type hints go in that direction. See especially this paragraph for more context: it is always going to be only runtime checks, and they are meant to be entirely optional (even by convention) even if some linters could make use of it. Even then, hints are meant to be used for duck-typing (i.e., to hint that methods take a Number rather than specifically requiring an int). Most languages in the C family are based on static typing and features in the direction of dynamic typing are incomplete, missing and always frowned upon (think about designing Java interfaces working with Objects instead of  interfaces).

Early binding vs. static binding and dynamic dispatch vs. static dispatch

Again, Python is completely dynamic and late bound. Everything is bound at the latest possible moment. If it were possible to bind calls after the process terminated, Python would do that.
The other languages we are considering here have significantly different approaches among themselves: Java uses dynamic dispatch/late binding; Go uses dynamic dispatch when methods are called on an interface, static dispatch when called on a struct. C++ by default uses early binding and static dispatch. When virtual is explicitly used, dynamic dispatch is used. Rust provides both static and dynamic dispatch: to get dynamic dispatch a trait object has to be used. General consensus (and standard library design) favor static dispatch any time that it is possible and it makes sense.

Object model

The object model of Rust (which technically speaking does not claim to be an OO language, so here we are a bit overloading the term) is remarkably different from classic imperative object oriented languages (e.g., Python, Java, C++). Python, Java and C++ also have massively different object models, but in a sense they are closer to one another than Rust is. On a superficial analysis traits seem to behave somewhat like interfaces, but I feel the differences are really deep. They are much closer to Haskell type classes or ML modules; I do not have much experience with Rust, but designing stuff with Haskell type classes is much different from designing stuff in Java (or Python). Really. As a side note, when trait objects are used, the behavior is rather similar to Go interfaces: the difference is that interfaces in Go are implicitly implemented, while in Rust you have to explicitly implement the traits. Go does not provide anything similar to Rust traits when used statically (there are huge discussions on Go lack of templates and related stuff, won't repeat it here).

Pragmatics

Python philosophy is to keep things as simple as possible and to abstract details away (especially low level details). On the other hand Rust was created with the idea of giving full control over low level details. If you do not want to do it (or do not have to do it) Rust might feel like an overkill. There might be still tons of reasons to use it, though.

Runtime

Python has a very rich runtime. In fact, it is so rich that entirely reasonable optimizations are completely impossible by design (unless you are extremely smart about it: see for example the fact that the function frame is an object and technically you might end up accessing it in some call downstream, so a lot of stack related optimizations are not really feasible -- note to self: check what Pypy guys do about it). Rust, on the other hand, almost does not have a runtime by design. The runtime is really minimal by deliberate choice (which makes a lot of sense considering the target)
Just to wrap it up, if I have to pick a single language in the C family to be closer to Python esthetics, that would be Go (with a honor mention to C itself). Both want to abstract the irrelevant details and the system (in one case the VM, in the other the compiler + runtime) does "the right thing". Both have similar issues when you need to go outside the box and need more control (FFI + cython on one side, unsafe + low level syscalls + FFI in the other).
On the other hand, Rust is much closer to C++ or OCaml: it is a rather complex language that enables fine-grained control on everything; however, it is often hard to relinquish such control: truth to be told, it seems that Rust is much better in this area, the model is simpler to reason with and saner, so the net result is that even if you can exercise a lot of low level control, it is not as painful as in C++, where sometimes the abstractions are all wrong.

Go and Python

Another important similarity between Python and Go is that after few weeks of study (days? depends on how fast one is to learn a programming language) it is feasible to read and understand all the code out there. If something is unclear, it is because of lack of familiarity with the domain (e.g., low level networking details) but not because of complex or unusual language features or magic.
If possible, Go is even more extreme in this than Python itself. There are rather basic concepts in Python that lots of coders do not understand and do not know how to use. Albeit I disagree, lots of people consider hard stuff like meta-classes, the descriptor protocol, coroutines implemented with generators and even the relatively simple decorators.
In Go there are no features with a comparable level of cognitive overhead: the language is extremely simple. You get few basic blocks with very simple semantics that can be composed to obtain arbitrarily complex effect. The net result is that even if the newbie might not know how to compose the blocks to get the intended result, he would still be able to understand a solution created by a more experienced programmer, understand which does what and why. This does not mean that writing in Go is necessarily simpler than writing in Python, just that it might be even simpler to read.

Other languages (such as C++, OCaml, Haskell and perhaps Rust) need much more understanding to be able to cover the same amount of code. To be entirely sincere, I have dabbled with Haskell for about ten years and to this day there is a lot of code that I need to unwrap, disassemble and study line by line to actually understand how it is working (what is doing is somewhat simpler, but appreciating the nuances of the language takes time). With Go I was reading the standard library implementations after 2 days.

In fact, when I fell for Python I was mainly using other stuff (including, stuff like Perl and C++). I used to mock lots of Python tenets. Then a guy I knew from some usenet group asked me to review a couple of his scripts. That was before github, before git was even imagined. It was a world of pain and CVS, and SVN was brand new and people often sent code via email and similar primitive means. But I am digressing... I knew something about networking, back then. Did not know Python at all, though. I was dubious that I could actually help him out; but then I realized I was perfectly capable of reading his code, without having ever written a line of Python, without even having read a tutorial, I might say, without even having read the wikipedia page of Python. Which would be surprising now, but really, back then Wikipedia was probably hosted on something less powerful than my mobile phone (and loading pages took tens of seconds, sometimes minutes). Nonetheless I could read and immediately understand the scripts. I could also easily modify them and make them work. Go is the second language after Python that gave me the same impression, which was the decisive factor in my decision to give it a deeper look.

Rust and Python (again): what about the juniors?

Back to the comparison between Rust and Python... one of the points raised in the discussion was whether some freshly trained junior Python programmer could easily take on Rust. To put a disclaimer here, the archetype of said programmer is not the smart college guy that will end up working for some IT colossus or über-cool startup, nor the garage hacker without formal education but with talent and brains in spades. It is more like the average IT guy, mildly skilled, mildly cable that can, with some supervision, work on a relatively trivial project with other people. To my surprise, there seem to be a lot of these guys. So... the question is, if one of such guys is trained in Python, which language from the C family can he more easily pick up?

The idea I have of Rust, is that it is not the language for him. I believe said novice would probably not even understand where to start. Lots of design decisions in Rust hold that some problems need to be solved in the most efficient way, so the language should provide the programmers control to specify everything to get to that most efficient way. The side effect is that somebody without experience in systems, low level programming and computer architecture would not even know that the choices Rust provides are solutions of real problems. Essentially, I am afraid, a lot of it would be taken just as magic, not as conscious trade offs between control and ease of programming. This is not a critique to Rust: I appreciate a lot its coherence to its goals. Among them there is not "being a language for unskilled novices".

I am also not suggesting that Java would be the best candidate here (really, I have no clues), but to make a comparison to write decent (not excellent, plainly decent) Java it is sufficient to have an adequate understanding of object oriented design. Essentially, it is quite impossible to write sensible Java without a solid understanding of object oriented programming (at least, of the flavor of OOP that Java promotes). And, on the other hand, with a solid understanding of OOP, the resulting Java tends to be fine (again, not perfect, not super smart or super efficient, but fine). The standard library is rich and well documented and there are widely used libraries that cut the few corners that were left (e.g., Guava, Apache Commons, Netty, etc.). So probably somebody with experience in Python and a decent understanding of the OOP part could be able to design in Java software that does not make me want to puke. Too much. And yes, a lot more needs to be taken into account to write great Java code (and the JVM is a tiny operating system on its own that needs to be understood, albeit that might be easier than getting the same level of understanding of the whole Posix model).

I would be very interested in seeing with would happen with Go. Probably the language is too young to spread in those contexts, but I would still love to see whether the transition from Python to Go is really as easy as it seems.

At the end of the day, the impression Rust left me (after a couple of days of study... so I might still be partial) is that of a language closer to a sensible version of C++ that does not drive people to Lovecraftian abysses of insanity and pain. The comparison is a huge stretch: I am talking about feelings here, not objective features. Rust is effectively much more functional than OOP and it does not really work "like C++". It does not at all. Again: the keyword here is "feeling". Also Rust is immensely agile for a language that offers such an astonishing level of detail on low level semantics.

On the other hand, Go seems as perfectly in between C and Python: it can be seen as a modernized C, or like a super-optimized Python. Go get it!