In the last few months, from time to time I experimented with the programming language
Go, a new systems language released only in 2009 and developed by people like Ken Thompson, Rob Pike and Russ Cox at Google. While still staying relatively low-level, it adds a garbage collector, and simple object/interface system, and the mechanisms of goroutines and channels as means of concurrency and inter-process communication (or should I say inter-goroutine communication).
In the last few days, I found more time to experiment with Go, and finally fully got the hang of the object system, and I must say that I really like it, both for the simplicity and the expressiveness. Objects in Go feel so simple, so natural, and at the same time powerful, with very little syntactic overhead. So that's what I want to write about today.
Let's start with a simple example:
Here we created a new type Vehicle with one private member "speed", and a method Speed() that returns the current speed. Of course, a vehicle all by its own isn't very useful, so we define the minimal requirements for a vehicle that's actually usable:
Here we define an interface named Drivable that defines the required methods for an object to be drivable, in our case, it must be able to accelerate, brake and show the current speed. Based on this, we construct our first actually usable vehicle:
And voila, we have our first Car that is also Drivable. What did we exactly do here?
We created a new type Car that is a struct, and embedded the type Vehicle: instead of adding a named member, we added an unnamed member, only specified by the type. This embedding also makes all available methods for this type available to our new type, i.e. a method Speed() is now also available for our type Car.
In addition, we implemented two new methods, Accelerate() and Brake(), in order to match the interface Drivable. And last but not least, we implemented a function to create a new Car.
Now, let's create another type of Drivable vehicle, let's say a boat:
So far, so uninteresting. The implementation of the boat is basically the same as the car, so nothing new.
But now I want to go a step further and implement and amphibious vehicle that is both a car and a boat. Let's just do that:
But that doesn't quite work out, because when we try to compile it, we will see an error message like this:
Since we embedded both Car and Boat into Amphibian, the compiler can't decide which Accelerate() method it shall use, thus communicating it as ambiguous. Based on that, it also says that it can't create an object of type Amphibian as Drivable because it has no proper Accelerate() function. This is the classic problem of diamond inheritance that we need to resolve here, not only for Accelerate(), but also for Brake() and Speed(). In the case of the amphibious vehicle, we do this by returning the right speed depending on whether the vehicle is in the water or on land:
And of course, the object perfectly works as Drivable:
So, what I just showed you are the capabilities in what Go modestly calls
embedding but that feels like multiple inheritance and is really simple at the same time.
For a more complete view on Go and its features, I recommend the official Go documentation, and especially "
Effective Go", a document that introduces the most important Go features and shows how to write idiomatic Go code. The complete example source code can be found
here.