Live data from Hacker News

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

joeshaw.org

301–310 of 311 posts

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

#301
post #235
post #155

Earlier quoted context omitted.

> 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…

> If anything, something not being in a try-catch block says that it will be handled higher up

I don't think you get what I'm saying. Some functions will always succeed. Some functions fail in obvious ways. Some functions fail in non-obvious ways. How do you know, as you're scanning a long block of code, which operations may fail, and which will always succeed?

For instance, suppose you have code like the following:

    // Decode they key JsonKeyGuids as type []Guid
    guids := JsonGetKey[[]Guid](&ru[i].Json, JsonKeyGuids)
Without looking at the function signature: If the key in the structure doesn't exist, what happens -- does it throw an exception, or return an empty value? Is it possible for JsonGetKey to fail to parse?

And while checked exceptions might help, it's not perfect: Suppose your code block calls functions a(), b(), and c(); all of them return ErrParseFail, but while it's pretty obvious that a() or c() might fail that way, it's not at all obvious that b() would.

Secondly, even for operations that are obvious may fail: maybe you, as a senior programmer who has programmed with exceptions for years, are paranoid enough that you're always thinking in the back of your mind "what happens if this fails?" But I very much doubt a junior programmer is going to have that habit. Part of the intent of Go was to have a language for Google which would allow junior programmers could come to a previously unfamiliar bit of code and be reasonably effective very quickly.

> 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

You'd almost never just print the result of error messages unless it's at the top level, or it's the equivalent of a script. In most cases, you bubble it up, often wrapping it with a message of what you were trying to do; e.g.:

   return nil, fmt.Errorf("trying to excluded guid list for %v: %w", ru[i].Id, err)
That way at the top level (or wherever you do log the message), you have a stack not just of the function names and line numbers, but what the program was actually trying to do, potentially with specific values involved.

Not having the equivalent of C's "must_check" is certainly a missing guard-rail in golang

> And what is clearer in terms of error handling...

It comes down to a judgement call. I think Golang's way is better. Yes, it makes the code look cluttered with exit paths, but that's because the code is cluttered with exit paths.

I can see that with experience, an exception-based developer would learn to see the implicit exit paths in most cases. So let me assert to you, that with experience, a check-the-return-values based developer also learns to filter out the explicit error paths to see the "happy path" algorithm clearly. But on the whole, I think the latter is likely to lead to fewer bugs, particularly for less experienced developers, but even for more experienced developers.

At any rate, now you've heard arguments for Go's error handling idiom; and if you don't agree, at least you can understand where the Golang crowd are coming from.

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

#302

Earlier quoted context omitted.

This ... depends. In normal POSIX land, hard links break the tree structure, for one, so you get a DAG but not a tree. I think some file systems do enforce tree structure, though - hard links are not supported everywhere. It used to be possible ages ago to hard link to directories, which meant that you could have actual cycles and a recursive tree-walking algorithm would never terminated. (As far as I know you can st…

> , hard links break the tree structure, for one, so you get a DAG but not a tree. More than DAGs, but instead pretty arbitrary graphs since you can express cycles with hard links.

> since you can express cycles with hard links

You can't if you only allow hard links to files.

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

#303

Earlier quoted context omitted.

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.

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

Could you recommend your personal favorite(s) of such libraries? Enquiring minds want to know! Thx.

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

#304

Earlier quoted context omitted.

> 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.

> Using a very well designed library for your use case is not nonsense. Could you recommend your personal favorite(s) of such libraries? Enquiring minds want to know! Thx.

When it comes to libraries that handle the messy details of putting data on disk, sqlite is the only one I can name off hand so that's the favorite.

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

#305
post #231
post #10

Earlier quoted context omitted.

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.

There is a lot of space between the spooky magic quasi-global errno integer and proper sum types. Go's errors are not magic globals nor are they mere integers, even if they aren't sum types either.

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

#306
post #231

Earlier quoted context omitted.

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.

There is a lot of space between the spooky magic quasi-global errno integer and proper sum types. Go's errors are not magic globals nor are they mere integers, even if they aren't sum types either.

How is it not just errno? Especially that POSIX mandates errno to be thread-local, so not even that is a difference. Just because there is some syntactic sugar that converts it to a slightly more descriptive type than an int, doesn’t make it different.

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

#307
post #306

Earlier quoted context omitted.

There is a lot of space between the spooky magic quasi-global errno integer and proper sum types. Go's errors are not magic globals nor are they mere integers, even if they aren't sum types either.

How is it not just errno? Especially that POSIX mandates errno to be thread-local, so not even that is a difference. Just because there is some syntactic sugar that converts it to a slightly more descriptive type than an int, doesn’t make it different.

Thread locals are not lexically scoped, they are not stored as part of the function call stack, and mutating them is not expressed anywhere in the function signature. They are global variables with thread-local storage, not local variables.

Go's error returns are not sum types, but they are product types. The return signature (T, error) indicates that two values will be returned essentially as a tuple by the function: one of type T and one of type error. Error-returning functions are pure functions (though they typically perform other, impure operations).

There is no syntactic sugar (both for good and for ill). The type of errors is an ordinary interface, with a single method. Any type can implement that interface, including strings and structs and slices. Errors can have as many contextual details as needed, including nested/wrapped error messages, specific parameters of loop iterations, multiple errors rolled up from multiple operations, etc.

Go's error return is just an ordinary but common use of its multiple-value returns. You can write a function/method that returns three ints and no errors:

    func (v Vector3) Splay() (x int, y int, z int)
You can even write a function that returns multiple errors:

    func DoThisAndThat() (thisError error, thatError error)
Try that with errno!

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

#308

Earlier quoted context omitted.

I don't know how you can confidently say "if close fails, who cares" all over this thread. If close fails, I wanna know and I wanna know why.

Let me introduce you to the question mark, seen on your screen as the '?' character. It indicates that a question is asked. This is important as... Never has "who cares" been confidently said. It has always been asked "who cares?". And not asked in a vacuum either, but specifically asked alongside the question of what is to be gained from the knowledge of the error. We now know that you allegedly care, which is a pro…

I care about it because it's unexpected. I seek to wrap my mind as fully as possible around the behavior of my programs. Unexpected behavior like this indicates there is something I do not understand. I cannot rely on my programs if I do not understand them. I cannot state they are robust and stable if I do not understand these sorts of details.

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

#309
post #306

Earlier quoted context omitted.

How is it not just errno? Especially that POSIX mandates errno to be thread-local, so not even that is a difference. Just because there is some syntactic sugar that converts it to a slightly more descriptive type than an int, doesn’t make it different.

Thread locals are not lexically scoped, they are not stored as part of the function call stack, and mutating them is not expressed anywhere in the function signature. They are global variables with thread-local storage, not local variables. Go's error returns are not sum types, but they are product types. The return signature (T, error) indicates that two values will be returned essentially as a tuple by the function…

> The return signature (T, error) indicates that two values will be returned essentially as a tuple by the function

Technically, yes, practically, it doesn’t tell you anything, as its most common usage is how it would be used as a sum type (either one or the other).

And yeah, I didn’t quite think of multiple return values, but that itself can be just syntactic sugar over an `out` parameter.

But these technical details aside, I am not convinced that it is not “as useless as errno-type error handling”, with the only caveat of it returning an interface that is slightly more informative.

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

#310

Earlier quoted context omitted.

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 y…

> Languages in which try/catch/throw work across coroutine boundaries You wha...? The question was about goroutines , not coroutines. Besides, you'll notice that exception handlers cross coroutine boundaries in Go just fine. Your random tangent isn't even correct. Where did you dream up this idea to the contrary? I know coroutines are still new to Go, only officially landing in the latest release (experimentally in 1…

I answered as I did because the original question seems strangely broken. The premise upon which the question is asked is flawed.

What are goroutines, other than peculiarly broken coroutines? (Notwithstanding your point that go has a non-broken implementation of coroutines at experimental release stage).

It is true that Javascript has a goroutine-like facility for executing coroutines on a seperate thread. But there are languages (c++, c# at least) where coroutines can execute on separate threads without suffering from the broken-ness of goroutines.

Post reply on HN