Good
new Types
1 | type UserId string // <-- new type |
Bad
Go ignored advances in modern language design
functional programing
No LLVM
GC
Interfaces are structural types
Go interfaces are like Java interfaces or Scala & Rust traits: they define behaviour that is later implemented by a type (I won’t call it “class” here).
No enumerations
iota
The:=
/var
dilemma
Go provides two ways to declare a variable and assign it a value: var x = "foo"
and x := "foo"
.
The main differences are that var
allows declaration without initialization (and you then have to declare the type), like in var x string
, whereas :=
requires assignment and allows a mix of existing and new variables. My guess is that :=
was invented to make error handling a bit less painful:
Zero values that panic
go have no constructor
1 | var m1 = map[string]string{} // empty map |
上面所示,m1初始化了,但m0没有初始化。。
Go doesn’t have exceptions.
just “Defer, panic and recover“
Ugly
The dependency management nightmare
Things are getting better though: dep
, the official dependency management tool was recently introduced to support vendoring.
But dep
may not live long though as vgo
, also from Google, wants to bring versioning in the language itself and has been making some waves lately.
mutability is hardcoded in the language
Go makes it easy however to copy an entire struct with a simple assignment, so we may think that passing arguments by value is all that is needed to have immutability at the cost of copying.
1 | type S struct { |
And the since built-in collections (map, slice and array) are references and are mutable, copying a struct that contains one of these just copies the pointer to the same underlying memory.
Slice gotchas
Slices come with many gotchas: as explained in “Go slices: usage and internals“, re-slicing a slice doesn’t copy the underlying array for performance reasons.
This is a laudable goal but means that sub-slices of a slice are just views that follow the mutations of the original slice. So don’t forget to copy()
a slice if you want to separate it from its origin.
Forgetting to copy()
becomes more dangerous with the append
function: appending values to a slice resizes the underlying array if it doesn’t have enough capacity to hold the new values. This means that the result of append
may or may not point to the original array depending on its initial capacity. This can cause hard to find non deterministic bugs. (在append的时候,如果没有足够的空间,会重新resize slice,这样pointer会乱指)
1 | func doStuff(value []string) { |
mutability and channels: race conditions made easy
As we saw above there is no way in Go to have immutable data structures. This means that once we send a pointer on a channel, it’s game over: we share mutable data between concurrent processes. Of course a channel of structures (and not pointers) copies the values sent on the channel, but as we saw above, this doesn’t deep-copy references, including slices and maps, which are intrinsically mutable. Same goes with struct fields of an interface type: they are pointers, and any mutation method defined by the interface is an open door to race conditions.
noisy error management
In “Error has values“ Rob Pike suggests some strategies to reduce error handling verbosity
1 | type errWriter struct { |
nil interface values
1 | type Explodes interface { |