Live data from Hacker News

Don't defer Close() on writable files (2017)

joeshaw.org

41–50 of 311 posts

Re: Don't defer Close() on writable files (2017)

#41
post #26

Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do? You'd just concentrate on the "happy path", you'd close the file, there'd be nothing to forget or write blog posts about because the exception would be propagated, without needing to write any lines of code.

This problem isn't solved with exceptions either. The problem is that finalizers (C++ destructors, Java's `finally` blocks, Go's `defer` etc.) shouldn't fail but `close()` can fail. Therefore, for 100% correctness, `close()` calls should be handled explicitly and not left to finalizers. Finalizers shouldn't fail because they might be executed while another exception is already in flight. Three languages have three di…

What about Java's try-with-resources?

Re: Don't defer Close() on writable files (2017)

#42
post #40
post #26

Earlier quoted context omitted.

This problem isn't solved with exceptions either. The problem is that finalizers (C++ destructors, Java's `finally` blocks, Go's `defer` etc.) shouldn't fail but `close()` can fail. Therefore, for 100% correctness, `close()` calls should be handled explicitly and not left to finalizers. Finalizers shouldn't fail because they might be executed while another exception is already in flight. Three languages have three di…

> in my opinion they all do the wrong thing What would be the right thing? Combining the original exception and the error from `close` into some kind of `MultipleError`?

Personally I do not believe the math of these two monads allows for any better solutions (and I do not believe multierror is correct ;P)... I am thereby also very curious what they think the correct thing to do here is.

Re: Don't defer Close() on writable files (2017)

#43
post #22

Earlier quoted context omitted.

> Explicit error handling is a choice Yeah, it is - a bad choice IMO. I know the "if err != nil" pattern is spoken up as some sort of cultural idiosyncrasy of Go, similar to the whitespace formatting in python. But so far, I haven't seen any actual data (or even arguments) why it is superior to exceptions, or which inherent problems of exceptions it solves. (The classical example of "it makes control flow more obviou…

Well, let's assume Go did commonly make use of exception handlers for error cases: defer func() { if r := recover(); r != nil { fmt.Println("Failed to write file", r) } }() f := os.Create("file") defer f.Close() io.WriteString(f, "Hello, World!") Cool. You've solved one problem layer, perhaps. But if you look closely you'll notice that code still has a bug! So clearly exception handlers aren't enough. There might be…

Maybe I'm too dumb too see the bug here.

Do you mean the issue that if both WriteString() and Close() throw an exception, the one from WriteString() will be swallowed?

That used to be a problem, but has been solved in more modern implementations using suppressed exception tracking.

Re: Don't defer Close() on writable files (2017)

#44

Surely this would all go away if Go had an exception handling mechanism like most mainstream languages do? You'd just concentrate on the "happy path", you'd close the file, there'd be nothing to forget or write blog posts about because the exception would be propagated, without needing to write any lines of code.

Go does indeed has an exception handling system like most other languages. Errors and exceptions are very different things, though. Of course, nothing stops you from building your own file handing package that overloads exception handlers to deal with errors. If it gains traction then it would prove the stdlib should consider a v2 API. But that you already haven’t done so is telling…

There's a self-selection problem though. People who prefer error handling through exceptions are just not going to use go. Period. Will not use it. Been there done that. No no no.

So it is telling. But I think what it actually tells is that people would have done it just use another language instead.

Re: Don't defer Close() on writable files (2017)

#45
post #22

Earlier quoted context omitted.

> Explicit error handling is a choice Yeah, it is - a bad choice IMO. I know the "if err != nil" pattern is spoken up as some sort of cultural idiosyncrasy of Go, similar to the whitespace formatting in python. But so far, I haven't seen any actual data (or even arguments) why it is superior to exceptions, or which inherent problems of exceptions it solves. (The classical example of "it makes control flow more obviou…

> I haven't seen any actual data (or even arguments) why it is superior to exceptions, or which inherent problems of exceptions it solves It's faster. Doing all error handling via exception is not viable if you want speed. Exceptions work well for errors in the sense that these rarely happen. But using them for general "this is the bad outcome" of an operation that can happen in the hot-path is problematic. For examp…

This is merely an implementation tradeoff: I could trivially modify a compiler to support exceptions that were as fast as returning error sum types without any syntax changes, but it comes at the cost of making all non-failing code slower--as you have to litter the runtime with checks that exceptions elide--so we don't generally do it... but like, if you make a new language and don't go with the syntax of exceptions because you like the implementation of sum types, I feel you have misunderstood the problem space.

(As for "handling" errors, you should only have a few places in the entire codebase which do that... littering the entire codebase with opportunities to feel like you might could handle an error seems like a mistake as it just encourages more handling.)

Re: Don't defer Close() on writable files (2017)

#46
post #43

Earlier quoted context omitted.

Well, let's assume Go did commonly make use of exception handlers for error cases: defer func() { if r := recover(); r != nil { fmt.Println("Failed to write file", r) } }() f := os.Create("file") defer f.Close() io.WriteString(f, "Hello, World!") Cool. You've solved one problem layer, perhaps. But if you look closely you'll notice that code still has a bug! So clearly exception handlers aren't enough. There might be…

Maybe I'm too dumb too see the bug here. Do you mean the issue that if both WriteString() and Close() throw an exception, the one from WriteString() will be swallowed? That used to be a problem, but has been solved in more modern implementations using suppressed exception tracking.

Ha. I suppose you could argue that is also a bug, but not the one I was thinking of. Let's say this hypothetical Go where error over exception handling is the norm has no such issue.

Think more carefully about what we're actually trying to solve. It is not just about errors.

Re: Don't defer Close() on writable files (2017)

#48

Earlier quoted context omitted.

You mean panics? They don't work across go routines so they're limited and hardly exception handling like most other languages

Which languages see exception handlers work across go routines? Anyway, most don't. There is no difference from exception handlers in most other languages. The syntax is a little different. Is that where you've become confused?

Languages in which try/catch/throw work across coroutine boundaries: Java, Javascript, and it's a standard feature in C++ coroutine libraries (and completely supported by the core C++ coroutine engine). So in my limited personal experience, among the languages that I am familiar with ... all of them except go.

Are there actually ANY languages other than go that have coroutines, and try/catch/throw mechanisms, where you cannot throw across a coroutine boundary?

And why would exception handlers NOT work across coroutine boundaries, other than laziness on the part of implementers?

Re: Don't defer Close() on writable files (2017)

#49
post #40
post #26

Earlier quoted context omitted.

This problem isn't solved with exceptions either. The problem is that finalizers (C++ destructors, Java's `finally` blocks, Go's `defer` etc.) shouldn't fail but `close()` can fail. Therefore, for 100% correctness, `close()` calls should be handled explicitly and not left to finalizers. Finalizers shouldn't fail because they might be executed while another exception is already in flight. Three languages have three di…

> in my opinion they all do the wrong thing What would be the right thing? Combining the original exception and the error from `close` into some kind of `MultipleError`?

That's probably the best option I think. I've heard Ada does that (but don't quote me on that). If you can access the original errors from the `MultipleError` object, at least you can tell the user what exactly went wrong.

I don't thing there's one true right thing™ though. That's why explicit handling is necessary: The compiler doesn't have enough context to handle it for you. The programmer needs to decide what's the right way to handle it.

Re: Don't defer Close() on writable files (2017)

#50
post #39
post #23

Here is my favorite solution to this problem // CheckClose is a utility function used to check the return from // Close in a defer statement. func CheckClose(c io.Closer, err *error) { cerr := c.Close() if *err == nil { *err = cerr } } Use like this - you must name the error return func whatever() (err error) { f, err := os.Open(blah) // ... defer CheckClose(f, &err) // ... } This closes the file and if there wasn't…

Which of course isn't really a "solution", as it results in ambiguous error semantics; this is especially the case with Go's defer, as it pushes the code to the end of the entire function, not merely some intermediate relevant scope... the result is that a file used near the beginning of a function might fail to close but that error will not be realized until after something else in the function fails, after the poin…

The solution is that there are multiple solutions that are suitable in different situations.

If you need "strict" requirements then it is likely impossible but you can get close enough if you use sqlite. Or more lightweight atomic/thread-safe/fault-tolerant file library. You could rollout your own: it easy to start, and continue until the error rate is tolerable for your application (though it may take more dev time).

If you don't need db-like strict guarantees. Just write your app knowing that it may fail (data may be lost, corrupted). It may be ok in a lot of cases.

Post reply on HN