Go is one of the best tools out there today for heavy lifting and backend code. It’s my go to language when it’s time to bring out the big guns and I enjoy working with it immensely. That being said, its type system is an embarrassment. It’s meant to be clear and comprehensible yet it’s full of awkward surprises.
Lack Of Generics
Most modern type systems have some notion of generics. Generics allow types to interact with other types in a type-safe manner. Go has no support for generics. It’s possible to live without generics by using type assertions (casting) and empty interfaces but these are not checked at compile time. Some built-in functions of Go work with pseudo-generic types (e.g. len(), append()) so the language designers are clearly aware of the problem, and yet this pseudo-generic functionality is not exposed to regular users of the language. The compiler and the type system are there to help the developer; having to cast types back and forth in a statically typed language is just embarrassing.
Confusing Semantics Of Exported Identifiers
Many modern languages provide some mechanism to hide methods and data internal to various compilation units or data structures. This is a good thing; it makes programs more reliable by limiting interfaces exposed by modules and by abstracting away implementation details.
Most programming languages allow this by having a mechanism to expose or hide functions, data fields, and types. Go does this with a twist: it exports the lexical identifier, not program element (e.g. type or function) the identifier represents. As a consequence, if there are no identifiers involved, unexported program elements can leak into other packages. What does this mean? Consider the following example:
package p1
type hidden struct {
Field int
}
func NewHidden() hidden {
return hidden{Field:1337}
}
package main
import (
"fmt"
"p1"
)
func main () {
myHidden := p1.NewHidden()
fmt.Println(myHidden.Field)
myHidden.Field = 9000
fmt.Println(myHidden.Field)
}
This example will compile and work. Go will have absolutely no problem with this as it’s not the “hidden” type that is unexported, only its identifier. Since the:=operator infers the type of the variable on its left side from the expression on the its right, there are no unexported identifiers involved. People on the golang-nuts mailing list seem to think that this makes the type system “easier to understand and explain” but in my opinion these sort of unintended consequences only make things worse.
In addition, the reflect package does not allow reading or writing struct fields with unexported identifiers (even if the field was reached without using any identifiers) which seems to completely contradict the philosophy of exporting being a strictly lexical concept.
The Verdict
If we were still living in the ’90s or Golang was a hobbyist or experimental programming language, these shortcomings would be acceptable. The year is 2014 however and Golang is heavily backed by Google itself so there is simply no other way to say it: Go’s type system is an embarrassment.
I don’t believe that the lexical exporting issue will ever be resolved, partly because it appears to be an inherent part of the language and partly because I don’t think the designers of the language will ever admit it’s an issue. On the other hand, I’m almost certain that generics will be added at some point, hopefully sooner rather than later. That will make things better.