Relearning Go: Day 8
Stateful Goroutines to Recover
The bugle sounds, and we hear…
Stateful Goroutines
Instead of sharing state, use messages to communicate state. This is one of Go’s strengths, I believe, so this might have been better placed before atomics and mutexes, which we looked at yesterday.
Reading through the example – mediating access to a map that’s held by a goroutine – I get a sense of why this is a useful pattern. Were the map to be an expensive resource, something with a bit more heft, then we might also want to employ some of the earlier patterns, like rate limiting. It’s all goroutines and channels, meaning composing these together ought to be a breeze.
Sorting & Sorting by Functions
gobyexample.com/sorting
gobyexample.com/sorting-by-functions
Why is there an interface cmp.Ordered
but
also the builtin comparable
?
The
comparable
interface may only be used as a type parameter constraint, not as the type of a variable.
Is this compiler magic from the Before Times, i.e. Before Generics? I guess I’m saying I don’t know how to reconcile these things into my mental model of Go quite yet.
In addition, Ordered
cannot be implemented by us mortals; its powers are
reserved for the Gods atop Mount Golympus. Frankly, this sucks.
Sorting is fine, but it shines a light on how Go is haphazardly put together.
Panic!
Note that unlike some languages which use exceptions for handling of many errors, in Go it is idiomatic to use error-indicating return values wherever possible.
This seems to be the way many languages are going, and I’m a fan. Shame Go’s error idiom sucks though.
Defer
I wrote a mini-rant about defer
yesterday. Here’s a fragment:
Because
defer
is the way to arrange clean up, that’s the tool one reaches for instinctively – even if, for example, we’re allocating expensive resources in a loop. Imaging writingdefer expensiveThing.Drop()
inside a loop – because that’s how we ensure that things are cleaned up – only to find out in production that it crashes with an out of memory error. I suspect that’s an easy mistake.
Sleeping has not improved my opinion of it. Control flow is not self evident from the syntax, but, more importantly, how does it behave?
- In what order do deferred functions run?
- Can a deferred function write to named output parameters?
- Can a later deferred function see those writes?
- If a deferred function panics, are other deferred functions called?
- What happens to values returned from a deferred function?
There are surely answers to all of these questions, but I must dig for them. I bet that most Go developers don’t know them without looking.
It sweeps the dirt under the rug.
Why bother with defer
as a language construct? Instead, we could borrow the
context manager approach from Python:
func with[T any, U any](build func() T, use func(T) U, finally func(T)) U {
// …
}
In practice that’s going to be unwieldy and verbose, passing all those functions
in, and then Go’s error idiom will complicate things, but it’s a bit more
obvious what’s going on. To address the unwieldiness, this could be codified
into a language construct instead. Let’s call it… try
:
if f, err := os.Create(p); err == nil {
try {
// Do stuff.
}
finally {
// Close the file.
if err := os.Close(f); err != nil {
panic(err) // Or whatever.
}
}
} else {
panic(err) // Or whatever.
}
I’m not a fan of this suggestion, but at least we can see the control flow. At least we could use the knowledge and intuition we’ve developed about the language to answer my earlier questions.
Lastly, defer
is often/mostly used to ensure that resources are cleaned up,
but it’s easy to forget; there’s nothing forcing me to use it. Even C++ has a
more elegant solution.
Recover
To recover from a panic, one must defer
a function that calls recover()
. We
get back to my questions above about defer
? This is a mess.
This could have been a single function:
func recover[T any](act func() T) (T, error) {
// … internal implementation …
}
func main() {
if _, err := recover(mightPanic); err != nil {
fmt.Println("Panic:", err)
}
}
Is that not clearer?
Next time: string functions.