Hacker News new | ask | show | jobs
by divan 3596 days ago
> Go is a language that pushes remembering corner cases and failure conditions onto the programmer

Can you elaborate on this? I write go for 3+ years and I have no idea what corner cases and failure conditions do you mean.

4 comments

Examples that bother me sometimes. YMMV of course.

Writing to a closed channel panics, but writing to a nil channel blocks forever.

Appending to a nil slice works fine, but inserting into a nil map panics.

If you have a function that returns an error struct, and you wrap it with another function that returns the error interface, nil returns from the inner function will no longer test equal to nil.

Defining a method with a receiver type of Foo, rather than <star>Foo, means all modifications to the Foo get silently dropped. This can also happen to methods that correctly take a pointer receiver, if their caller incorrectly takes a value receiver.

Maps are not threadsafe/goroutine-safe.

Expression evaluation order is not defined, and varies between compilers. (https://github.com/golang/go/issues/15905)

What you describe is all true, but honestly, this is such a small number of corner cases compared to traditional languages(C, C++, python, javascript, etc.), that it is really not a big deal. Also most of this is clearly documented.
Agreed about C/C++/JS, though I'm curious to hear your thoughts about Python. There are only two really common gotchas in Python that I tend to notice:

- Using an iterator more than once silently produces nothing. I notice this when I insert print statements to debug something, but then accidentally turn the following for-loop into a no-op. The Python 3 change that made more top-level functions return iterators made this problem more common, though I agree with the performance justification for doing it. It would be nice if iterating again after hitting the end raised an Exception, though I'm sure that would break all sorts of code that assumes it doesn't.

- Mutating function default arguments affects all subsequent calls. This is most common Python gotcha people seem to talk about.

A corner case about Go which makes me absolutely crazy: calling Reset() on a timer which has already fired has the biggest gap between "What I expect to happen" and "What actually happens" of any stdlib I've ever worked with.

https://gist.github.com/patio11/bc883d566778c323742432c203e6...

(You can see it here in the playground, but try it on your local machine if you don't believe me and/or think the playground has an inconsistent understanding of what time actually means: https://play.golang.org/p/ltdV9dI609 )

You never drained the longTimer channel, so when you say "We agree that longTimer has fired, right?"; that's not quite true. After you call Reset(), you're still getting the value from the first firing, because that's the first time you read from the channel at all.

The docs are quite clear on this behavior and say "Timer will send the current time on its channel after at least duration d." -- key words being at least and says nothing about when you choose to read from the channel.

It's even worse. The longTimer has fired, and it sent a message on the channel just as it was supposed to. When Reset() is called, it causes a second firing and a second message. Here is the code, corrected to illustrate. The output times are exactly as one would expect.

https://play.golang.org/p/lntgH6tkiF

This particular issue is unfortunate - it can't be changed without potentially breaking existing programs.

See https://github.com/golang/go/issues/11513 and https://groups.google.com/forum/#!topic/golang-dev/c9UUfASVP... for some background.

Thanks for sharing! I read up on it. This whole "channel draining" concept seems powerful.
All standard libraries have their quirks, corner cases and peculiarities. This is not specific to Go. Or are you saying it is worse on Go?
I presume an example would be something like remembering to check `rows.Err()` in `package sql` at the end of iterating through all rows. If you check the error each iteration while calling `rows.Scan()` but forget to check the `rows.Err()` at the end, it could potentially be much, much later that you find out something went wrong.
Methods in the standard library panicking if the method receiver is nil for one.