The blog post mentions panic as "always fatal to your program, game over", which is fine. Now " Defer, panic and recover " that predates it, explains how to recover from panics by actually catching them , and says "For a real-world example of panic and recover, see the json package from the Go standard library".
And indeed, the json decoder has a common error handling function that just panics, the panic being recovered in the top-level unmarshal function that checks the panic type and returns it as an error if it's a "local panic" or re-panics the error otherwise losing the original panic's stacktrace on the way. So Go does have exceptions, uses them internally but tells you not to.
Fun fact: a non-googler fixed the json decoder a couple of weeks ago to using regular errors bubbling up. Update: since this post was written, Go 1.
This solves most of the issues explained below, although the choice of the minimum version for dependency resolution is debatable see also this post on why Rust's Cargo chooses the maximum version. If dependency management is not resolved for another year, I am considering about quitting Go and never coming back. Dependency management issues routinely reverse all the joy I get from the language. Let's put it simply: there is no dependency management in Go.
All current solutions are just hacks and workarounds. This goes back its origins at Google, which famously uses a giant monolithic repository for all their source code. No need for module versioning, no need for 3rd party modules repositories, you build everything from your current branch. Unfortunately this doesn't work in the open Internet. What version?
The current master branch at the time of cloning, whatever it contains. What if different projects need different versions of a dependency? They can't. The notion of "version" doesn't even exist. Want to have your projects cleanly organized in a separate directory? The community has developed workarounds with a large number of tools.
Package management tools introduced vendoring and lock files holding the Git sha1 of whatever you cloned, to provide reproducible builds. Finally in Go 1. But it's about vendoring what you cloned, and still not proper version management. No answer to conflicting imports from transitive dependencies that are usually solved with semantic versioning.
Things are getting better though: dep , the official dependency management tool was recently introduced to support vendoring. It supports versions git tags and has a version solver that follows semantic versioning conventions.
It's not stable yet, but goes in the right direction. 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. So dependency management in Go is nightmarish. It's painful to setup, and you don't think about it while developing until it blows up when you add a new import or simply want to pull a branch of one of your team members in your GOPATH There is no way to define immutable structures in Go: struct fields are mutable and the const keyword doesn't apply to them.
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. However, and unsurprisingly, this does not copy values referenced by pointers. 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.
So you have to be extremely careful about this, and not assume immutability if you pass a parameter by value. There are some deepcopy libraries that attempt to solve this using slow reflection, but they fall short since private fields can't be accessed with reflection. So defensive copying to avoid race conditions will be difficult, requiring lots of boilerplate code. Go doesn't even have a Clone interface that would standardize this.
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. In the code below we see that the effects of a function appending values to a sub-slice vary depending on the capacity of the original slice:.
Go concurrency is built on CSP using channels which makes coordinating goroutines much simpler and safer than synchronizing on shared data. The mantra here is " Do not communicate by sharing memory; instead, share memory by communicating ". This is wishful thinking however and cannot be achieved safely in practice. 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.
Let Her Go by Passenger | Lyrics with Guitar Chords (Easy Version)
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. So although channels apparently make concurrent programming easy, they don't prevent race conditions on shared data. And the intrinsic mutability of slices and maps makes them even more likely to happen. Talking about race conditions, Go includes a race condition detection mode , which instruments the code to find unsynchronized shared access. It can only detect race problems when they happen though, so mostly during integration or load tests, hoping those will exercise the race condition.
It cannot realistically be enabled in production because of its high runtime cost, except for temporary debug sessions. Because Go claims to not support exceptions although it does , every function that can end up with an error must have an error as its last result. Your eye will quickly develop a visual filter for this pattern and identify it as "yeah, error handling", but still it's a lot of noise and it's sometimes hard to find the actual code in the middle of error handling.
There are a couple of gotchas though, since an error result can actually be a nominal case, as for example when reading from the ubiquitous io. Reader :. In " Error has values " Rob Pike suggests some strategies to reduce error handling verbosity. I find them to be actually a dangerous band-aid:. Basically this recognizes that checking errors all the time is painful, and provides a pattern to just ignore errors in a write sequence until its end.
So any operation performed to feed the writer once it has errored-out is executed even if we know it shouldn't.
Let It Go (Disney song)
What if these are more expensive than just getting a slice? We've just wasted resources because Go's error handling is a pain. What if evaluating parameters has side effects? We've just introduced a serious bugs to simplify error handling So Rust 1. So you have the terseness of the above code while keeping a correct error-handling.
Transposing Rust's approach to Go is unfortunately not possible because Go doesn't have generics nor macros. This is an update after redditor jmickeyd shows a weird behaviour of nil and interfaces, that definitely qualifies as ugly. I expanded it a bit:. The above code verifies that explodes is not nil and yet the code panics in Boom but not in Bang. The explanation is in the println line: the bomb pointer is 0x0 which is effectively nil , but explodes is the non-nil 0x10a,0x0. This is because interface values are fat pointers. The first element of this pair is the pointer to the method dispatch table for the implementation of the Bomb interface by the Explodes type, and the second element is the address of the actual Explodes object, which is nil.
The call to Bang succeeds because it applies to pointers to a Bomb : there is no need to dereference the pointer to call the method. The Boom method acts on a value and so a call causes pointers to be dereferenced, which causes a panic. So how should we write the test in a safe way? We have to nil-check both the interface value and if non-nil, check the value pointed to by the interface object Bug or feature?
The Tour of Go has a dedicated page to explain this behaviour and clearly says "Note that an interface value that holds a nil concrete value is itself non-nil". Still, this is ugly and can cause very subtle bugs. It looks to me a like a big flaw in the language design to make its implementation easier. These are struct tags which the language spec says are a string "made visible through a reflection interface and take part in type identity for structs but are otherwise ignored".
So basically, put whatever you want in this string, and parse it at runtime using reflection. And panic at runtime if the syntax isn't right. This string is actually field metadata, something that has existed for decades in many languages as " annotations " or "attributes". With language support, their syntax is formally defined and checked at compile time, while still being extensible.
Why did Go decide to use a raw string that any library can decide to use with whatever DSL it wants, parsed at run time? Things can get awkward when you use multiple libraries: here's an example taken out of Protocol Buffer's Go documentation :. Side note: why are these tags so common when using JSON?
Hence the need for tedious tagging.