griping about go
defer is a pretty good way to clean up after errors. The problem is it’s enticingly like RAII but doesn’t really get there.
It’s only function scoped. Lots of times I have a loop opening files, and I want to make sure I close it. defer doesn’t work here. Usually this can be resolved by creating a new function and calling that from the loop. But frequently not. Calling a new function means no access to existing variables, no creating new variables, etc. Unless one creates an inline function. But that still damages flow control. No continue, no break, etc.
The arguments to defer are mostly what you want, but the evaluation order occasionally requires some thinking. There’s some (probably esoteric) cases where value vs references change what happens. Go’s embrace of passing small structs by value means you’re never too far from a surprise though.
Using recover is pretty strange. I think I like lua’s approach here.
There needs to be a way to undefer something. defer should return a handle for cancellation. Otherwise this requires another inline function to capture and evaluate a cancellation boolean. (I wouldn’t object to more robust cancellation in general. We’ve got context now, but where’s my pthread_killitwithfire?)
One of the things I appreciate about go is that many interfaces are defined in terms of readers, as opposed to passing large byte slices or strings around. In theory, this should be more efficient. Don’t want to buffer an entire http request in memory, right? But I think sometimes it’s taken too far.
Sometimes you really do start with a byte slice, and the reader is inconvenient. You can’t seek or rewind without resorting to maybe ok casting introspection. It’s hard to share a reader between threads. So then I end up reading everything into a bytes buffer, defeating the efficiency.
Sometimes a stacked reader will close the bottom reader. Sometimes it won’t. So a TLS connection closes the network connection, but a gzip reader doesn’t close the file. This inconsistency requires careful reading of the documentation.
Sometimes it’s ok to close a reader (or any closer) twice. Sometimes it’s not. So this requires more careful reading.
And then we add defer into the mix. I open a file or connection, so I defer closing it, then I stack another io on top. Do I defer closing that one too? Do I need to cancel the first defer?
There are some other inconsistencies. Where’s the interface for flush? I need to flush both bufio.Writer and gzip.Writer, but only the latter can be closed. It’s difficult to cook up an interface that works with everything. (There’s a flusher hiding in net/http, but it feels weird reaching all the way over there.)
The standard package way to deal with this seems to be to define a whole pile of private interfaces and toss arguments into casts until you get something you like. I’m not sure I’m ready to program like this.
It’s not all bad.