v, err := foo.Open()
// …
defer func() {
if closeErr := v.Close(); closeErr != nil {
err = fmt.Errorf("while closing %w: %v", err, closeErr)
}
}()
// …
When you’re writing something trivial/pure, Go’s error handling is fine, if maybe a tad bit verbose, but it quickly becomes nightmarish when you start to do nontrivial things that are typical for systems programming.FWIW I love Go, it’s my daily driver for most things. I still think it can get messy far too quickly
In fact it’s quite common to “commit” on close, at least from what I’ve seen.
close(2) does not "commit". You have to call v.Sync() (i.e. fsync(2)) for that.
From man 2 close:
A successful close does not guarantee that the data has been successfully saved to disk, as the kernel uses the buffer cache to defer writes. Typi‐
cally, filesystems do not flush buffers when a file is closed. If you need to be sure that the data is physically stored on the underlying disk, use
fsync(2). (It will depend on the disk hardware at this point.)I was not talking about file descriptors; rather I was talking about the fact that it’s common for Go libraries to do interesting and useful things when you call the `Close()` method on a type. `Close()` is idiomatic go for resource cleanup.
You might wait until close to populate a length and or checksum field in a message header. Or close could submit a buffer of log events to a remote aggregation endpoint.
I’m not saying I agree with APIs that are designed that way, but it’s common enough that you should assume the error return value is significant unless you know otherwise (for Go)
The language’s status quo forces everyone to think about errors more deeply than in other languages and acknowledges that the error case is as critical and worthy of the programmer’s attention.
Not really. Rust also forces you think deeply about errors but don't bother you with verbose syntax. I think Swift was also similar.
x := FallibleFunction() ? err -> return fmt.Errorf("something happened %v", err)
Doesn't really change that, but significantly reduces the amount of noise from error handling boilerplate. And (most importantly to me) reduces the amount of vertical space taken up by error handling code, which makes it easier to follow the happy flow.And while handling errors is important, it is also often trivial, just optionally wrapping the error and returning it up to the caller. I agree it is good for that to be explicit, but I don't think it needs to take up more space than the actual function call.
In my experience, the important error handling code is either at the lowest layer, where the error initiall occurs, or at the top level, where the error is reported to the user. Mid level code usually just propagates errors upwards.
gofmt is the good bit about working in Go. Pretty much everybody uses it, and so you can use it too. Some other languages have similar tools, but they're not as pervasive, so it's far too easy to end up in a situation where you can't use the tool because it would just make too much of a mess of the inconsistently manually-formatted stuff that's already there.