Live data from Hacker News

Go subtleties

harrisoncramer.me

141–150 of 190 posts

Re: Go subtleties

#141
post #130

I balked a little when the article refers to format strings as "string interpolation" but there's multiple comments here running with it. Am I out of date and we just call that string interpolation these days? I also found this very confusing: > When updating a map inside of a loop there’s no guarantee that the update will be made during that iteration. The only guarantee is that by the time the loop finishes, the ma…

Mutating maps during iteration is a big red flag.

Yep. Which is part of why I'd much rather talk accurately about it being the iterator that may or may not encounter newly added values.

That makes it a lot clearer where the problem is, which is also where the solution is: get the list of keys you want to work on ahead of time and iterate over those while modifying the map.

Re: Go subtleties

#142
post #98

Earlier quoted context omitted.

Agree the ship has likely sailed, but if it could be addressed wouldn't it be nice to remove nil value interfaces altogether? Maybe start by letting new interface types declare/annotate that they don't box nil values? Then one day that becomes the default. Oh well.

It's not that the ship has sailed, it is that if you sit down and sketch out what people think they want it is logically incoherent. What Go does is the logically-coherent result of the way interfaces work and the fact that "nil" values are not invalid . It is perfectly legal for a "nil" pointer to validly implement an interface. For instance, see https://go.dev/play/p/JBsa8XXxeJP , where a nil pointer of "*Repeater"…

Your blog posts (that I read a few weeks ago) and your comment here are the best explanations I've ever read on this topic. You're not just looking at the surface of the problem, but diving in the why it is like that, semantically. I really like that you mentioned dependent typing in your conclusion.

Re: Go subtleties

#143
post #127

Great list! Reminds me to check out more of the new stuff in 1.25. The one thing I wish Go had more than anything is read-only slices (like C#). The one thing I wish more other languages had that Go has is structural typing (anything with Foo() method can be used as an interface { Foo() }.

Yeah, having mutability optional would be great. It would also allow a lot of data to pass through the stack instead of heap due to pointers, which Go is riddled with for absolutely no reason(imo). On the other hand, now that we have iterators in Go, you can create a wrapper for []byte that only allows reading, yet is iterable. But then we're abstracting away, which is a no-go in Go and also creates problems later on…

> Yeah, having mutability optional would be great. It would also allow a lot of data to pass through the stack instead of heap due to pointers, which Go is riddled with for absolutely no reason(imo).

My guess is that it is due to many developers bringing reference semantics with them from other languages to Go. It leads to thinking about data in terms of pointers instead of values.

Re: Go subtleties

#144

Earlier quoted context omitted.

This was fixed in Go 1.23 https://go.dev/doc/go1.23#timer-changes They will get cleaned up.

I meant that the goroutine in the example keeps running. Often this contradicts the reason for having the timeout in the first place.

I'm not sure what you mean? The examples all return from their function.

Re: Go subtleties

#145
post #122

Earlier quoted context omitted.

I like to say this: "Only my code is allowed to be clever" But, on a serious note, I agree with you. Go lacks a lot of power, especially in its type system, that causes a ton of problems (and downtime) that in other languages is trivial to prevent statically.

out of curiosity (not meant snidely), do you have an example of a case where the weaker type system resulted in serious problems?

Pretty much any null pointer deference error ever?

But it is hardly ever the weak type system that is at fault, just good use of a stronger type system could have prevented the issue.

Once you start to make "invalid states unpresentable" and enforcing those states at the edges of your type system suddenly a lot of bizarre errors don't happen anymore.

Re: Go subtleties

#146

Earlier quoted context omitted.

And code with zero ability to do fancy trickery ("expressive" as some people like to say) is easy to read even if the codebase - or even the language - is unfamiliar. Which is really handy when shit's on fire and you need to find the error yesterday. You can just follow what happens instead of trying to figure out the cool tricks the original programmer put in with their super-expressive language. Yes, the bug is on…

> And code with zero ability to do fancy trickery ("expressive" as some people like to say) is easy to read even if the codebase - or even the language - is unfamiliar. A mate of mine did Comp Sci back in uni when First Years were taught Turbo Pascal showed me some, when I was still doing stuff in ZX Spectrum BASIC and Z80 assembler in high school. It was immediately clear what was going on, even if the syntax was a…

I always reiterate to junior programmers that you write as clever code as you want.

On your own time.

When you're writing code for work, stuff that other people have to eventually read and understand, you be as boring as possible. Skip all the tricks and make code readable, not cute. Someone might have to understand and fix it at 3 in the morning while everything is on fire.

  > Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

Re: Go subtleties

#147
post #138

Earlier quoted context omitted.

Size classes would save some space and speed up like-to-like comparisons, but wouldn't really do much for unlike comparisons (especially vs. float or complex). Looking only at type pointers fails to account for custom types (e.g., type Foo int); remember that an untyped integer constant can be compared with these. If you want the same semantics at runtime as you get at compile time, I don't see how you can get much s…

But the LHS can determine how it compares to the RHS when the RHS is determined to be an untyped constant? Or instead of saying RHS (my mistake), let's say the typed side since comparisons are symmetric. A bit like having a special method attached to the type strictly for comparisons? That would be much less expensive than such a type switch if I am not mistaken. Would handle custom types as well. If promotable from…

Ok, I think I follow. Instead of putting the comparison logic on the untyped side, you'd put it on the typed side. In code, reusing imports and untypedInt declaration, but replacing the method from before, you'd have:

  type intType interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 }
  func equals[I intType](x I, y any) bool {
    switch val := y.(type) {
    case I: return x == val
    case untypedInt: return val.i.IsInt64() && val.i.Int64() == int64(x)
    default: return false
    }
  }
And this would need a separate specialization for unsigned integers, floats, and complex numbers. This approach saves us from having to introspect the underlying type at runtime, but the example is incomplete. We also have float and complex untyped constants, so now each concrete type has to switch on all of the untyped constant forms it compares with. Still, it might be faster, though I'm not sure how much it reduces code bloat in practice (it's nice to not need the reflect package though).

[edit: side note, I was trying to actually write out all the code that would be needed, and I discovered that you can't call real or imag on generic types: https://github.com/golang/go/issues/50937]

Re: Go subtleties

#148

Earlier quoted context omitted.

It's fantastic concise language and standard library steered by people who are determined to keep it simple and intuitive... which IMO makes it all the more odd that it has this obvious foot gun trap where `!= nil` doesn't always mean what you might think.

The “simplicity” of Go is just virtue signaling. It has gotchas like that all over the language, because it’s not actually simple.

> because it’s not actually simple

Cue Rich Hickey's Simple made Easy: https://www.youtube-nocookie.com/embed/SxdOUGdseq4 / https://ghostarchive.org/varchive/SxdOUGdseq4

Re: Go subtleties

#149
post #87

I had a “wtf” moment when using Go around panic() and recover() I was so surprised by the design choice to need to put recover in in deferred function calls. It’s crazy to smush together the error handling and normal execution code.

It's cause it's not normal error handling to use recover(). In smaller codebases, panic probably should not be present. For larger codebases, recover should be in place only in very very sparse locations (e.g. at the top level http handler middleware to catch panics caused by unreliable code). But in general, returning errors is supposed to be how all errors are signaled. I've always loved the semantic distinction be…

Recover() has its sharp edges. If some downstream function part of the execution stack does not release a critical resource because its execution was halted midway by the bubbling panic, then the resource will leak (and in the case of a locked sync.Mutex, subsequent Lock()s will deadlock).

Until go1.23 [0], Recover() comes in handy for fault reports, however; ex: https://github.com/hashicorp/terraform/blob/325d18262e/inter...

[0] which introduced debug.SetCrashOutput: https://pkg.go.dev/runtime/debug#SetCrashOutput

Re: Go subtleties

#150
post #6

Earlier quoted context omitted.

I guess as a corollary, Go really rewards writing the dumbest code possible. No advanced type shenanigans, no overuse of interfaces, no complex composition of types. Then you will end up with a very fast, resource light system that just runs forever.

And code with zero ability to do fancy trickery ("expressive" as some people like to say) is easy to read even if the codebase - or even the language - is unfamiliar. Which is really handy when shit's on fire and you need to find the error yesterday. You can just follow what happens instead of trying to figure out the cool tricks the original programmer put in with their super-expressive language. Yes, the bug is on…

Yeah, so pretty much every large project has concurrency bugs in Go. Because before generics, there was no way to write a parallel for-each loop without shooting yourself in the foot a couple of times.

Go is simple just like assembly is simple.

Post reply on HN