←back to thread

Go subtleties

(harrisoncramer.me)
235 points darccio | 7 comments | | HN request time: 0.863s | source | bottom
Show context
DarkNova6 ◴[] No.45667140[source]
As somebody who only views Go from a distance, I see this list as a combination of „what‘s the big deal?“ and „please don‘t“.
replies(2): >>45667192 #>>45672374 #
OvervCW ◴[] No.45667192[source]
I'm amused by posts like this because it shows that Go is finally slowly moving away from being an unergonomically simplistic language (its original USP?) to adopt features a modern language should have had all along.

My experience developing in it always gave me the impression that the designers of the language looked at C and thought "all this is missing is garbage collection and then we'll have the perfect language".

I feel like a large amount of the feeling of productivity developers get from writing Go code originates from their sheer LOC output due to having to reproduce what other languages can do in just a few lines thanks to proper language & standard library features.

replies(8): >>45667300 #>>45667358 #>>45667372 #>>45667409 #>>45667468 #>>45667838 #>>45668913 #>>45675568 #
1. 0x696C6961 ◴[] No.45667358[source]
If you think Go and C are that similar then you don't know either.
replies(2): >>45667436 #>>45667558 #
2. quietbritishjim ◴[] No.45667436[source]
Go and C have partially shared origins. Two of the three creators of Go (Ken Thompson and Rob Pike) were involved in the early days of C. Ken Thompson is even the creator of B, the predecessor of C. There are obvious huge differences between the language but in a more subtle way they're actually quite similar: C is an "unergonomically simplistic language", just as the parent commenter describes Go.
replies(1): >>45668760 #
3. OvervCW ◴[] No.45667558[source]
They are similar in the sense that there are very few abstractions, relying on the programmer to reimplement common patterns and avoid logical mistakes.

You have to put thought into such things as:

- Did I add explicit checks for all the errors my function calls might return?

- Are all of my resources (e.g. file handles) cleaned up properly in all scenarios? Or did I forget a "defer file.Close()"? (A language like C++ solved this problem with RAII in the 1980s)

- Does my Go channel spaghetti properly implement a worker pool system with the right semaphores and error handling?

replies(1): >>45668701 #
4. 0x696C6961 ◴[] No.45668701[source]
> Did I add explicit checks for all the errors my function calls might return?

You can easily check this with a linter.

> Are all of my resources (e.g. file handles) cleaned up properly in all scenarios? Or did I forget a "defer file.Close()"? (A language like C++ solved this problem with RAII in the 1980s)

You can forget to use `with` in Python, I guess that's also C now too eh?

> Does my Go channel spaghetti properly implement a worker pool system with the right semaphores and error handling?

Then stop writing spaghetti and use a higher level abstraction like `x/sync/errgroup.Group`.

replies(1): >>45668971 #
5. 0x696C6961 ◴[] No.45668760[source]
Pike was not involved with the design of C. He was involved with Newsqueak and Limbo which inspired Go's concurrency model.
6. OvervCW ◴[] No.45668971{3}[source]
>Did I add explicit checks for all the errors my function calls might return?

You can check anything with a linter, but it's better when the language disallows you from making the mistake in the first place.

>You can forget to use `with` in Python, I guess that's also C now too eh?

When using `with` in Python you don't have to think about what exactly needs to be cleaned up, and it'll happen automatically when there is any kind of error. Consider `http.Get` in Go:

resp, err := http.Get(url)

if err == nil { resp.Body.Close() }

return err

Here you need to specifically remember to call `resp.Body.Close` and in which case to call it. Needlessly complicated.

>Then stop writing spaghetti and use a higher level abstraction like `x/sync/errgroup.Group`.

Why is this not part of the standard library? And why does it not implement basic functionality like collecting results?

replies(1): >>45669722 #
7. amiga386 ◴[] No.45669722{4}[source]
This seems like a judiciously designed API to me.

You don't need to check if err was nil before calling resp.Body.Close()

https://pkg.go.dev/net/http#Get

> When err is nil, resp always contains a non-nil resp.Body. Caller should close resp.Body when done reading from it.

https://pkg.go.dev/net/http#Response

> The http Client and Transport guarantee that Body is always non-nil, even on responses without a body or responses with a zero-length body. It is the caller's responsibility to close Body.

Calling http.Get() returns an object that symbolises the response. The response body itself might be multiple terabytes, so http.Get() shouldn't read it for you, but give you a Reader of some sort.

The question then is, when does the Reader get closed? The answer should be "when the caller is done with it". This can't be automatic handled when the resp object goes out of scope, as it would preclude the caller e.g. passing the response to another goroutine for handling, or putting it in an array, or similar.

Go tooling is more than happy to tell you that there's an io.ReadCloser in one of the structs returned to you, and it can see that you didn't Close() it, store it, or pass it to somewhere else, before the struct it was in went out of scope.