Hacker News new | ask | show | jobs
by parhamn 999 days ago
It really doesn't help that the major (perhaps only?) official resources on generics in golang are these blog posts [1][2] and the spec. And now this blog post.

The whole "what type am I getting"/make()ing is really tricky (as outlined in this doc) especially when its a pointer/interface/slice/etc. And a lot of feels like it doesn't need to be as much of a complex decision tree as it is. Is there any other documentation on this stuff that I'm missing?

Theres a lot of complication buried in golang people don't talk about that much. nil vs empty slices, interface{} and any behavior differences, make() and what it would do for various type scenarios, impossible to remember channel semantics (which ones panic again?). Of course, theres always a good explanation for why it is the way it is, but for a language so opinionated, stronger opinions on better DX in the deeper parts would be great.

[1] https://go.dev/blog/intro-generics [2] https://go.dev/doc/tutorial/generics

4 comments

Pointing people at the spec is probably the best decision the Go team ever made. Why wonder about things when you can have true authority in an honestly very short piece of text? No guessing, just an exact description of what's going to happen.

(I was in there recently reading about operator precedence recently and discovered an operator I didn't know existed, "bit clear (AND NOT)", &^. Amusingly, I needed to do that operation but wasn't sure if I needed parentheses for X & (^Y) or not. I still don't know why it's a dedicated operator, however. The spec rarely says WHY, just WHAT.)

It does help that the spec (and memory model!) is quite good and reasonably concise. I send people there as often as possible when they start getting interested in learning things For Real, blogspam is rarely anywhere near as useful despite being multiple times longer.

(Which is not meant to claim this article is blogspam - making recommendations with examples is quite different from a spec, and we definitely need recommendations)

Yeah. Effective Go feels a little dated now. My most frequent link is to Code Review Comments: https://github.com/golang/go/wiki/CodeReviewComments
> Why wonder about things when you can have true authority in an honestly very short piece of text?

There is no such short piece of text because

> The spec rarely says WHY, just WHAT.

The one that got me was the implicit wrapping of structs into interface types. As an example, the below code segfaults with `fatal error: panic while printing panic value: type runtime.errorString`:

    package main
    
    type SomeError struct {
        SomeMessage   string
    }
    
    func (se *SomeError) Error() string {
        return se.SomeMessage
    }
    
    func doSomethingSomeWay() *SomeError {
        return nil
    }
    
    func DoSomething() error {
        return doSomethingSomeWay()
    }
    
    func main() {
        err := DoSomething()
        if err != nil {
            panic(err)
        }
    }

      func doSomethingSomeWay() *SomeError {
Yeah, you'd never return a concrete error like this -- clear red flag in any code review.

For precisely the reason you're demonstrating here! (Among several others.)

100% agree.

But do we have linting tools that raise this as an issue?

"Linting" this isn't really possible, because it's really beyond what a linter can handle. It would take a full static analysis job to determine that there's a problem, because there isn't any individual line of code that's wrong.

In this code, the error lies in the combination of all three of:

1. doSomethingSomeWay unconditionally returns a nil pointer.

2. That nil pointer claims to implement the error interface, but it is lying. If you actually call error on it, it will panic no matter what. But since the nil is not removable (no non-nil pointer types) this sometimes happens in real code, even though in this case it's obviously a faked up problem.

3. The main function packs the *SomeError into an error, then calls the method on it that will crash.

As this code. I would call it wrong if it had the type signature "func () error"; then it is definitively the part of the code that is lying and packing a value into an interface that does not implement that interface. But the example as written doesn't quite have any one line or function that a linter, generally simpler than static analysis, can pick up on.

(You may be surprised that I don't simply blame the code in main. I can explain why: https://www.jerf.org/iri/post/2957/ and a followup https://jerf.org/iri/post/2023/value_validity/ . It's actually an entry point into a very important thing for high-level programmers to understand, even well beyond Go.)

As jerf says, not really easily, no. But code review is a perfectly reasonable mechanism for catching this kind of thing.
I can't answer that, but the poster mentions it segfaults so it's identified that way for sure.
nil interfaces are not equal to nil values in Golang. Interfaces are fat pointers with a type and value pair and if the type is populated then the pointer as a whole will not compare to the `nil` value as it requires both elements to be nil. Frankly, I think this was a design mistake.
Yes, this has to do with interface values and how interfaces are implemented in Go. It's a bit weird.
Go101 is a jewel of a resource. It’s a spec written by an impartial observer not worried about trying to make golang look good but instead giving you the understanding to avoid all the edge cases

https://go101.org/

Eh, I can't agree. I'm sure there is useful signal in there, but the author has a highly idiosyncratic view of the language, recommends tons of "optimizations" based on benchmarks that often aren't sound, and has several bits of code that are simply incorrect.

As one example, the IsClosed function in https://go101.org/article/channel-closing.html is definitely not correct. The author doesn't seem to be aware of the comma-ok form of a channel receive as documented in https://go101.org/article/channel-closing.html -- which would be the correct way.

Many others. Reader beware.

Never seen this before but that’s awesome!
> interface{} and any behavior differences,

I'm curious now. They are type aliases. In which situations there is a difference in behavior?

There are no differences. You can always substitute one for the other without change in behaviour.

These two are interchangeable:

   func f[T any]() {}
   func f[T interface{}]() {}
and these two are interchangeable:

   func f(x any) {}
   func f(x interface{}) {}
Maybe OP means the difference between using an interface as type parameter constraint and using an interface as a function parameter / variable type.
No difference as any := interface{}
I was firing that list off the top of my head and I think that one was a mistake. My bad.

But, I did recently have some weird typing issues around "satisfies" x vs "is" x and using new() constructors of the T in a generic functions (particularly de-alternative serialization of proto.Message types) and had quite a bit of frustration and confusion. That may have been on me, but I did open my statement with "[wish it had better docs]".