Go has been getting a lot of attention lately. I’d like to bring up somethign I haven’t seen a lot of: Go’s relation to Java. Now, I don’t know anything for sure about the Go developers, but I’m sure being inside Google they see a lot of Java code. Go certainly is in some ways a reaction to more complex classical OO languages like C++ and Java (and C# and D, etc, etc), but it also has a lot of respect for what Java did right, even when it uses vastly different names and syntax for it’s solutions.
The one obvious point of comparison is in the concept of interfaces. Now I won’t dwell on this too long, but Go refines Java’s best idea, making it so you don’t need to opt in to satisfy an interface. This makes things a lot more lightweight. Go makes interfaces even more central than Java does. Java has class polymorphism, abstract classes, and interfaces. In Go every type is distinct, but any type with appropriate methods can be used with an interface.
Both Go and Java decided to punt on generics initially, and use typesafe casting where you’d need generics. This brings us to where Go learns a lession from Java: binary compatibility is too costly. Java has always had the idea of cross-platform binary compatibility. In practice this isn’t very useful. Making seperate builds for seperate platforms isn’t too much of a problem if source compatibility is good (which it is for Go), and when it came time to add generics to the language, they had to be crippled to support older JVMs. The thing they both get right, is to release a useful tool knowing it will need to grow. Go is not complete, it will probably get generics at some point, but for now, you can still get work done without them. Compare this to a language like Rust, which is much more complex and is still not really a practical language to use for a real project yet.
One thing that Go gets a lot of flack for is lack of exceptions. In reality, Go has the exact same distinction as Java: in Java there are regular Exceptions, and RuntimeExceptions. Regular Exceptions are checked at compile time, so they’re part of the API of any library you call (even if they are often not treated that way). In theory this eliminates one of the big problems with exceptions: you never know what exception will come from where. However, in practice checked exceptions are extremely verbose. RuntimeExceptions on the other hand may be thrown and caught anywhere, and are used mainly in two cases: programmer errors (which should never be caught), and things outside of your control (out of memory, etc). It’s pretty rare you’ll actually want to catch these. Go has two types of error handling: error codes (using multiple return values, so it’s cleaner than in C), and panics which are somewhat similar to exceptions. Panics are fundamentally like RuntimeExceptions – you use them either internally to a package, or they’re for programmer errors. Error codes are for expected errors, things like files not found, network timeouts, etc. Multiple return values allowes you to use error codes to cleanly solve the same problem as checked exceptions: making errors part of API of a particular package, while still having them be out-of-band of the normal return value of a funcion or method. Both checked exceptions and error codes are verbose (though error codes in Go seem less verbose to me), but that’s because dealing correctly with errors is hard and involves a lot of code.
Both Go and Java have a baked-in concurrency model. It’s not really a headline feature in Java anymore, but any method can be marked
synchronizedand will take a lock on the object it’s a part of. C and C++ (until last year) don’t bake in any single threading model, leaving that to libraries. Java realized that proper concurency requires language support, and in the 90s threads and locks were what everyone used. Nowadays they the consensus seems to be that using straight threads and locks doesn’t scale. Too many people just add
synchronizedanywhere they have a deadlock, which kills the concurrency and is usually voodoo programming. Go uses lightweight threads and message passing, which is much easier to understand for multiple reasons. You might argue that in 20 years we might be saying the same about the Go concurrency model as we’re saying now about Java, but I doubt it. Go’s concurrency model is based on CSP, which has only become more and more relevant over the years.
So Go tries to solve a lot of the same problems as Java, but it ends up with very different solutions to those problems. I think that Go solves these problems better than Java, but only time will tell. Java 1 was very simple, but nowadays Java (more the libraries and frameworks than the language) is practically synonymous with complexity.