Live data from Hacker News

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

joeshaw.org

231–240 of 311 posts

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

#231
post #10

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.

Explicit error handling is a choice and implicit error handling through exceptions is not necessarily a feature. Both have advantages and disadvantages, I’d say the more “modern” approach actually the opposite to what you state here, and is in my opinion the way to Go (pun int intended), though it’s also how Haskell does it. You’ll find the same philosophy in Rust, Zig, Swift and others which all build on the previou…

Go is doing the worst thing possible. It is neither expressive enough for proper sum types, nor does it have expressions (that are analogous to sum types with good defaults and syntactic sugar).

It is literally C’s shitty `errno` with syntactic sugar.

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

#233

Earlier quoted context omitted.

Sqlite is much better than raw files to keep data intact on power failures, and be sure to study the options carefully If you truly need to use files you can take other steps such as mv the old file to .bck before mv the new file, but I really think you want sqlite

SQLite doesn't do any magic other than fsync. Using it to deal with power failures is nonsense.

I think the idea was to store data in the db, instead of scattered over multiple files and directories. Then you only have to worry about one file to fsync (Two if using a wal).

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

#234

Earlier quoted context omitted.

Is… NOT ignoring errors just not an option? I don’t get it. If you propagate errors up but not all the way to being handled, haven’t you failed in a very simple, easy to fix way? Should you have a linter catching these things?

In this case the issue is that defer is a very good way to ensure you don’t forget to close the file in any branches, but a bad way to return values (you have to set the value of a named return variable, which is one of Go’s odder features). > Should you have a linter catching these things? JetBrains’ GoLand will in fact warn you of this. If the error truly is immaterial you can instead do defer func() { _ = f.Close(…

> […] you have to set the value of a named return variable

Ahhhh okay I see it now. I definitely prefer to not use that feature as well, and I’m surprised it’s even there given how well the rest of the language adheres to “only one way to do things”. Doubly agree that it’s a strange “hack” for forwarding the deferred return value… oof

> JetBrains’ GoLand will in fact warn you of this

Heh yeah that’s what prompted me to ask, as I noticed (and very much appreciated) these hints. 100% agree with the verbose-but-explicit example you gave, and do that myself.

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

#235
post #155
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…

> But so far, I haven't seen any actual data (or even arguments) OK, here's an argument. - In order to write resilient software, programs must handle not only the "happy path" when things succeed, but the path where things might fail. - Thus it is important for developers to 1) be aware of which operations may fail fail, and b) think about what the program should do in that case. - Exceptions make it easier for the p…

> In order to write resilient software, programs must handle not only the "happy path" when things succeed, but the path where things might fail

And exceptions let you handle error conditions without making the actual business logic harder to read, with as little or much specificity as required.

> Thus it is important for developers to 1) be aware of which operations may fail fail, and b) think about what the program should do in that case

Checked exceptions/effect types exist, being explicit or implicit in function signatures is not a fundamental property of exceptions.

And what is clearer in terms of error handling — if err being every third line, with questionable handling logic, e.g. just printing or swallowing stuff (or gestures at the article), and definite human error from repetition —— or a well-defined block with proper scoping, without which the error case does the only reasonable thing — automatically bubbles up, making it possible to handle higher up. There is often no immediate action that can be done in certain exceptional situations, e.g. your ordinary function that writes a file can’t do anything about a full disc. The best it can do is to yell, so that the action that called it somewhere can do some evasive action, e.g. re-trying/notifying the user/etc.

> Exceptions make it easier for the programmer to forget that something might fail, and to avoid thinking about what to do if it does fail.

Disagree. If anything, something not being in a try-catch block says that it will be handled higher up (or checked exceptions making it part of the signature), and when it’s surrounded by it, I know what is the happy path, and unhappy path immediately, without it being crossed over (usually badly), as it would happen with if errs.

> Go's error handling idiom makes it clear that an operation might fail

What about the case when it both returns a value and an error?

> and prompts programmers to think about what to do in that case

Blindly if erring and printing out a random string is not error handling. That’s just noise, and a terrible trap for yourself, having to grep for useless error codes later on.

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

#236

Earlier quoted context omitted.

Please no. Just handle errors from `close()` when you're writing files. > On modern hardware if a disk is throwing an io error on write you are having a bad day. And how would you know you're having a bad day if apps ignore those errors?

I'm arguing that the proper thing to do here is to kill the process along with whatever else is using the block device. Whether you handle the error or immediately or if you allow the error to occur after a defer, you still are almost certainly not handling it properly and are taking a speed hit for your troubles.

Absolutely not. Imagine a text editor failing when I hit save. Do you really want that to crash the application? No. You want a fallback to ctrl+A ctrl+C, paste into a email and send to yourself. Giving the user a choice to save on a different drive is also a possibility, maybe your thumb drive just got a nudge and lost connection for a second.

This is really all normal use cases under normal conditions.

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

#237
post #36
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…

You interpret Go using a different pattern for error handling as a criticism of or challenge to exceptions. Consider that languages can take different approaches, without having to get into “best” or “worse.” Every approach to error handling has advantages and shortcomings. The Go designers have explained and talked about this decision many times. Some people don’t like it and maybe choose a different language. No on…

> Every approach to error handling has advantages and shortcomings

If under ‘every approach’ you explicitly exclude go’s terrible errno syntax sugar, and include exceptions and sum types, then yeah. There is zero advantage to go’s error handling compared to the proper sum typed solutions.

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

#238
post #172

Boggles my mind that after more than 60 years of computer science, we still design tools (programming languages) where the simplest tasks are full of gotchas and footguns. This is a great example.

> the simplest tasks

The tasks seem simple from 30,000 feet up in the air. Once you get down into the dirt you realize there's absolutely nothing simple about what you're proposing.

A filesystem is a giant shared data structure with several contractual requirements and zero guarantees. That people think a programming language could "solve" this is what is boggling to me.

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

#239
post #37

AFAIK Python/C# use a similar approach to Go - instead of `defer`, they have `using`/`with` statements. Go's `defer` seems more flexible though - it can execute custom code each time it's used, whereas `using`/`with` always call `__exit__`/`Dispose`. How does the Python/C# approach compare to Go's in this situation? How are errors in `__exit__`/`Dispose` handled?

From the application point of view, exceptions are more natural to catch, so if the close inside the exit-function throws, the nearest catch block will not be far up the stack. Compared to go, where you are much more unlikely to have a recover block, because it is considered such a rare case.

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

#240

Earlier quoted context omitted.

Sqlite is much better than raw files to keep data intact on power failures, and be sure to study the options carefully If you truly need to use files you can take other steps such as mv the old file to .bck before mv the new file, but I really think you want sqlite

SQLite doesn't do any magic other than fsync. Using it to deal with power failures is nonsense.

> SQLite doesn't do any magic other than fsync.

True.

> Using it to deal with power failures is nonsense.

Using a very well designed library for your use case is not nonsense.

Post reply on HN