This week, Rob Pike talked to our class about Go, the programming language developed at Google. It was designed to help combat problems of scale which are frequently encountered at Google, due to a large number of both programs and data, as well as software engineers. Pike began by talking about Go’s features, which were made to address Google’s specific issues, but are also quintessential to many languages. In particular, Pike talked about the “abilities”, philosophies and ideas upon which Go was designed. The first was readability, which addresses Google’s problem of an overwhelmingly large codebase and employee base. That is, the language they developed had to be, more importantly than its ability to be written, able to be read easily and by as many people as possible. Go thus strives to maintain a “balance between clarity and redundancy”. Pike then talked about scalability for the massive amounts of data, computers, and code Google must compile and test daily. He then stressed suitability for which Pike noted that although Go is currently quite popular and used in many applications, its original purpose was to help Google’s software engineering, and thus is definitively not the panacea to the various problems which could plague users with different needs. Finally, he talked about “toolability”, or the ability of a language to have tools written for it, allowing users to write their own programs or fixes to address any problems they have with the language.
When we discuss scalability in the context of programming languages, we must consider several principal dimensions: cores, code, data, speed of compilation, speed of testing, and engineers. Go seeks to solve each of these scalability issues in a concerted effort that no other language has done. Being a product of Google, it is only natural that Go has consistent support for concurrency to take advantage of Google’s seemingly endless machine farms. To leverage the growing number of engineers at Google, Go was designed for large code bases with many developers pushing changes concurrently. For instance, consider dependency management in Go vs. C++. In C++, dependencies can grow exponentially and are often redundant. Go, on the other hand, hoists dependencies resulting in exponentially less data read than with C++’s includes. Because Go was designed with very specific requirements from its initial formation, its syntax has been optimized for readability, clarity, and adaptability, ultimately making it a very scalable language.
Scaling software usually involves concurrency and parallelism. These two things are similar, but they are far from being the same. While parallelism is dividing the work of one task between workers, concurrency is handling several different tasks at once. As Rob Pike put it, “Concurrency is about dealing with lots of things at once. Parallelism is about doing lots of things at once. Concurrency is about structure, parallelism is about execution.” Writing concurrent code is a matter of structural complexity, as the different processes have to be able to communicate with each other so that the code doesn’t crash. Implementation of concurrent programming is a relatively recent development, and as such, older languages like C and Java only have workarounds to it, which can make concurrent programming in those languages complicated. Go, on the other hand, was written with concurrent programming in mind, packing primitives and tools to accommodate it.
Functions in Go run as independent goroutines, which are like threads, but much less expensive because they live on the same address space as the other goroutines. Goroutines can communicate with each other through channels, for example, if you wanted to synchronize them somehow, a channel would allow you to block goroutines until others finish. Go is also able to act depending on which channels are ready to communicate through the use of the select statement, which is like switch, but for channels. With Go’s concurrency paradigms and built-in concurrency tools, designing concurrent code, as well as writing it, becomes much simpler.
Rust is another systems language that was recently developed by Mozilla Research and is aimed at being a memory safe, fast, and concurrent programming language. Rust is a lot newer than Go, and it’s design has a slightly different focus. Much of the design of Rust has been influenced by the shortcomings of previous systems languages like C, whose programs are often plagued with memory leaks. In terms of syntax, Rust resembles C with its braces and similar keywords; however, the language itself contains many semantic differences which make it a lot more flexible such as the explicit distinction between mutable and immutable variables as well as type inference so Rust’s compiler can automatically discern the type of certain variables.
Furthermore, in order to handle memory safety Rust has introduced a new concept of “ownership” which replaces historical models of allocating memory. Whereas in C the developer must keep track of allocating and deallocating memory, in Rust this is handled by creating an “owning handle” whenever memory is allocated. If this handle goes out of scope the memory is deallocated automatically. This means that unlike Go, Rust does not have a garbage collector built into the core language, which has spurred much debate over the performance/safety comparisons of both these languages.
As a new programming language, Rust presents many promising benefits but also faces many challenges ahead such as gaining user adoption and proper dev tooling. It is currently making good progress as seen in it’s release of version 1.0 alpha at the beginning of January 2015, which will prevent any major breaking changes to the language and core libraries going forward. Now developers can safely adopt Rust in to their systems with a guarantee that future iterations of Rust will not introduce major breaking changes to their code. This will help propel Rust to create a large and growing developer ecosystem similar to Go.