Google’s Go Language has been making news since the publicity launch in November ‘09. Go combines the development speed of working in a dynamic language like Python with the performance and safety of a compiled language like C or C++.
Go is not like Java and it doesn’t aim to be like Java. Rather, think of it as C++ for the 21st century. No … correct that - it’s a bit more than C++ but yet rather a lot less than C++. Maybe better to think of it as C done right. Its headline claims are that it is fast, safe, concurrent, fun and open source. Lets take a look at each of these.
Go compilers produce fast code fast. Typical builds take a fraction of a second yet the resulting programs run nearly as quickly as comparable C or C++ code.
Eyebrows have been raised at this aspiration. Yes, we know why we want fast code but many commentators have wondered why we should want fast code fast. Fast run-time performance yes … but what’s so good about fast compilation time?
For me, the answer stares us in the face: the language has been designed to be lean and mean and the effort needed by the compiler is an indication of this. Maybe a few ‘nice to have’ language features have been sacrificed for this goal. It might be said that Go is the antithesis of Ada, which is a large clever language but requires large clever tools and matching people to operate them. And Go dispenses with the old C notion of header and source files; in that sense it’s a bit like Java yet it goes much further.
But will Go lead to faster development? That depends to some extent on how well its tools mature and how well people using it adapt their mindset to the Go way of working - every language has its own mindset and Go’s is quite novel in several ways, as we’ll see. Anyone who tries to write C-like programs in it (or worse, Java-like) will miss the point and not get the best out of Go.
Go is type safe and memory safe. Go has pointers but no pointer arithmetic. For random access, use slices, which know their limits.
Type safety was once the darling of the well-trained software engineer. Again, Go points us to a dichotomy: strongly-typed languages have their proponents and have achieved a great deal. But there are many who say their strictness has a high price in development effort; it’s not surprising there are so many weakly- and un-typed languages battling to grab attention (for example the “P-languages”, Perl, PHP, Python). Go is cleverly designed to provide type-safety and without inheritance, but instead making aggregation a first-class concept. This is an interesting new idea; it will be fascinating to see how well it works out when people try to use it for large systems.
C is now an old language in more ways than one. Go brings to the C world a heap memory with automatic garbage collection, which is a great benefit both to programmer productivity and to program correctness. Although a garbage collector doesn’t guarantee there will be no memory leaks, at least it means that the job of cleaning up used resources is automatic so developers can focus on the more important issue of what needs to be kept vs what needs to be released.
Array slicing is a
very useful technique rarely seen in major languages. In C++ & Java there is simply no direct
equivalent. Java2 Lists do sub-lists, providing a similar function at the API level. In Go, slices are
part of the language syntax and make arrays much more powerful.
Conversely, many of Go’s features are
deliberately simple. An example is that there are only two visibility levels, public and private, and the difference
is simply based on whether a leading capital letter is used for the name (or not, in the case of private names).
Some examples of the better simplicity are arguably really beneficial but may draw criticism from die-hard
C/C++/Java developers who happen to prefer the older more complex ways. For example the Go
statement has no fall-through so doesn’t require
breaks in each branch. Instead, it allows a comma-separated list of
expressions in each case. Indeed, unlike C a comma is not an operator, but just separates the items in lists. There
are many more examples of simplifications like this that make Go a very no-nonsense language, much more
straightforward for new programmers but perhaps harder for those retraining from C++ or similar.
Go functions are sharply different from those in C/C++ . They can have output parameters as well as input parameters and may have a “receiver” - a special parameter consisting of a pointer to a struct and making the function into a method. There is no other way to make classes and methods, so this takes some getting used to. But it’s simple enough and produces clear readable code.
Interfaces feature strongly in Go.
One thing that Posix threads and Java taught the world is that concurrent software is hard. It is hard … but only if you start from the wrong point of view. It seems strangely at odds with our day to day experience of the truly concurrent world around us.
A crude generalisation the ‘American’ model of concurrent software is being concerned with how to share stuff in memory between your threads. Conversely, the ‘European’ model (at least amongst academic institutions) has been to pass messages between threads, each with their own totally private memory spaces. Clearly, C++ and Java show that the American approach won. Oxford, Cambridge and Edinburgh theoreticians have been ignored.
Until Go came along. The developers of Go have the succinct mantra:
Do not communicate by sharing memory. Instead, share memory by communicating.
Is this really right? … surely it’s easier to write a sequential program and then work out how the threads will carve up the shared data using mutexes and condition variables. That’s what everyone does so it must be right, yes?
Well … no. The Go approach always works out better in the end … the reason is simple: scalability. Go is based on CSP, which has a notion of composition. You can design small components containing one or more threads of execution each with some private data and some connecting communication channels. Some of those channels may emerge as an external interface of your component. The component behaves as a single unit that can itself be connected up to other units through those channels. This super-component itself can be treated as a single unit as part of a larger design. We have compositional building blocks that can be constructed this way ad infinitum. The underlying mathematics guarantees this. We can reason about the internal design of each component separately from other components, so our heads don’t explode with having to think of everything all at once.
This really works. So remember: do not communicate by sharing memory; instead,
share memory by communicating. To take a counter example, try using
in Java. There are no clear compositional semantics;
what happens in one bit of code directly affects another bit. It soon becomes nigh impossible to understand fully,
so deadlocks et al are almost impossible to
eliminate. Fortunately, Java APIs provide higher-level primitives so very few developers ever need to write
notify(). But the difficulty of scaling up concurrency remains.
And Go goes one big step further than the likes of Java and C++:
Go promotes writing systems and servers as sets of lightweight communicating processes, called goroutines, with strong support from the language. Run thousands of goroutines if you want—and say good-bye to stack overflows.
Go implements a much cheaper model for threads, meaning you can have a lot of them and they switch their contexts very quickly. Actually, Go is following paths already trodden by the likes of Erlang and occam (the language that probably can claim the fastest context switch times of any language). So it is possible to write Go models of highly concurrent physical systems using highly concurrent Go implementations. Whereas Java imposes much complexity by requiring thread pooling to overcome the high cost of threads, this is not so in Go.
Compared to Java, which I think is the main competitor to Go in this area, Go's goroutines and channels are just so much easier to work with than Java threads and locks, there's just absolutely *no* comparison at all. Go pretty much destroys the competition in this area.
Go has fast builds, clean syntax, garbage collection, methods for any type, and run-time reflection. It feels like a dynamic language but has the speed and safety of a static language. It's a joy to use.
Does Go bridge the culture gap between adherents of strictly-type languages and their free-and-easy adversaries? If so, it may quite quickly gain following in open source movements for which joy of use is a necessary (but not sufficient) part of job satisfaction. It will need this: Go is entering a crowded arena of languages and meta-description systems all competing for a share of the attention of developers and decision makers.
This almost goes without saying these days. It does what it says on the tin.
And what did they get wrong already?
struct- surely a symptom of job-half-done
- no exceptions - are we in the 1990s yet? [Update: panic() and recover() have been added]
- no generics - a nice-to-have that’s nice to have. Use Go interfaces instead.
Others have stronger views, for example see Michael Richter’s blog.
Go is a novel derivative from C that has been simplified and yet enhanced. The claims that it is fast, safe, concurrent and fun are all meaningful in the sense that they truly offer something new. This should put in in good stead for a strong uptake.
However, there are so many programming languages available, some very good and many also-rans. Go is competing in a crowded space. The main incumbents - e.g. Java in enterprise systems, PHP in smaller web applications, etc - are not going to suffer much challenge from Go. Where Go may make its mark is where C is currently used in systems programming. We’ll be watching that space with interest.comments powered by Disqus