12 ms·
Go subtleties
- rowanseymour 1y agoAh the old nil values boxed into non-nil interfaces. Even after 8 years writing go code almost every day this still bites me occasionally. I've never seen code that actually uses this. I understand why it is the way it is but I hate it.
- amelius 1y agoI ditched Go after an evaluation years ago. I can remember it was an issue with nil pointers being non-intuitive that turned me off. And exception handling. A pity because the runtime and ecosystem/community seemed pretty good.
- rowanseymour 1y agoIt'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.
- amw-zero 1y agoThe “simplicity” of Go is just virtue signaling. It has gotchas like that all over the language, because it’s not actually simple.
- LandR 1y agoYep. The lack of features means all the complexity is offloaded to the programmer. Where other languages can take some of the complexity burden off the programmer. Go isn't simple, it's basic.
- amelius 1y agoPerhaps Go is a nice target language for a transpiler, so you could still benefit from the runtime and ecosystem while fixing the bugs in the language itself. Anyone working on this?
- bobbylarrybobby 1y agohttps://github.com/borgo-lang/borgo https://github.com/borgo-lang/borgo
- laumars 1y agoAs someone who's written commercial software in well over a dozen different languages for nearly 40 years, I completely disagree. Go has its warts for sure. But saying the simplicity of Go is "just virtue signaling" is so far beyond ignorant that I can only conclude this opinion of yours is nothing more than the typical pseudo-religious biases that lesser experienced developers smugly cling to. Go has one of the easiest tool chains to get started. There's no esconfig, virtualenv and other bullshit to deal with. You don't need a dozen `use` headers just to define the runtime version nor trust your luck with a thousand dependencies that are impossible to realistically audit because nobody bothered to bundle a useful standard library with it. You don't have multi-page indecipherable template errors, 50 different ways to accomplish the same simple problem nor arguments about what subset of the language is allowed to be used when reviewing pull requests. There isn't undefined behaviour nor subtle incompatibilities between different runtime implementations causing fragmentation of the language. The problem with Go is that it is boring and that's boring for developers. But it's also the reason why it is simple. So it's not virtue signaling at all. It's not flawless and it's definitely boring. But that doesn't mean it isn't also simple. Edit: In case anyone accuses me of being a fanboy, I'm not. I much preferred the ALGOL lineage of languages to the B lineage. I definitely don't like a lot of the recent additions to Go, particularly around range iteration. But that's my personal preference.
- bobbylarrybobby 1y agoYou are comparing Go to Python, JS, and C++, arguably the three most complex languages to build. (JS isn't actually hard, but there are a lot of seemingly arbitrary decisions that have to be made before you can begin.) There are languages out there that are easy to build, have a reasonable std lib, and don't offload the complexity of the world onto the programmer.
- laumars 1y ago> You are comparing Go to Python, JS, and C++, arguably the three most complex languages to build. No, I'm comparing to more than a dozen different languages that I've used commercially. And there were direct references there to Perl, Java, Pascal, procedural SQL, and many, many others too. > There are languages out there that are easy to build, have a reasonable std lib Sure. And the existence of them doesn't mean Go isn't also simple. > and don't offload the complexity of the world onto the programmer. I disagree. Every language makes tradeoffs, and those tradeoffs always end up being complexities that the programmer has to negotiate. This is something I've seen, without exception, in my 40 years of language agnosticism and part-time language designer.
- ignoramous 1y ago> because it’s not actually simple Cue Rich Hickey's Simple made Easy: https://www.youtube-nocookie.com/embed/SxdOUGdseq4 https://www.youtube-nocookie.com/embed/SxdOUGdseq4 / https://ghostarchive.org/varchive/SxdOUGdseq4 https://ghostarchive.org/varchive/SxdOUGdseq4
- ignoramous 1y ago> And exception handling If you read & write Go regularly, the rather verbose error handling simply fades into the background. That said, errors in Go don't really translate to Exceptions as generally thought of; panic, however; may be does. Making changes to error handling wasn't for the lack of trying, though: https://news.ycombinator.com/item?id=44171677 https://news.ycombinator.com/item?id=44171677 > issue with nil pointers This is why most APIs strive for a non-nil zero value, where possible, as methods (on structs) can still dictate if it will act on a pointer. Though, I get what you're saying with Go missing Optional / Maybe / ? operator, as the only other way to warn about nil types is through documentation; ex: https://github.com/tailscale/tailscale/blob/afaa23c3b4/syncs/syncs.go#L148 https://github.com/tailscale/tailscale/blob/afaa23c3b4/syncs... (a recent example I stumbled upon). Static code analysers like nilaway (https://news.ycombinator.com/item?id=38300425 https://news.ycombinator.com/item?id=38300425) help, but these aren't without false positives (annoying) & false negatives (fatal).
- peterashford 1y agoI read & write Go regularly, and I hate its error handling intensely
- aatd86 1y agoYes, that'a bit too late after ten+ years perhaps but I wished we had a nil type and checking whether the interface is empty was a type assertion. In all other cases, like any(2) == 2, we compare the values. Then again that would mean that the nil identifier would be coerced into a typed nil and we would check for the nilness of what is inside an interface in any(somepointer) == nil. wrt the current behavior, it also makes sense to have a nil value that remains untyped. But in many other cases we do have that automatic inference/coercion, for instance when we set a pointer to nil.(p = nil) That's quite subtle and that ship has sailed though.
- rowanseymour 1y agoAgree 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.
- aatd86 1y agoOh that's probably doable. Introducing something like this is a bit orthogonal to the point above, but yes. It's not straightforward but probably something that will be considered at some point I reckon when thinking about making union interfaces first class. That will require to track a not nil typestate/predicate in the backend, something like that I guess.
- rowanseymour 1y agoHaving pondered on a bit more.. I think it's the struct that would declare that it's not usable as nil, and that in turn would tell the runtime not to box it if it's nil. That would also help the compiler (and copilot etc) spot calls on nil pointers which will panic.
- aatd86 1y agoBut that information disappears when you assign to an interface variable/container that is nillable. It requires an assertion to recover the info about the value inside the interface being not nil. basically `if v.(nil){...} creates two branches. In one we know v is not nil (outside the if block) and it can therefore be assigned to non nillable variables so to speak...
- kitd 1y agoThe advice I've read (and follow) is always to return values, not interfaces, from functions and test for nil against them. That IME tends to nip the majority of nil interface problems in the bud.
- YesThatTom2 1y agoExactly. I find that people try to use interfaces like they’re using an OO language. Go is not OO.
- leetrout 1y agoReturn concrete types, accept interfaces. Returning interfaces hides behavior and hampers evolution; accept interfaces so callers can swap implementations. For testing, mock the dependency, not the return value. Loudest arguments against returning concrete types were on the terraform core team and the excuse was it makes testing easier. I disagree.
- lenkite 1y agoThis advice of "returning concrete types" is in most cases a horrible anti-pattern that prevents evolution due to lack of information hiding. It has been also deliberately broken in the standard library in several places. This "advice" cannot be generically applied. Places where it is has been deliberately broken: net.Dial (Conn, error) image.Decode(r io.Reader) (Image, string, error) sha256.NewXXX() hash.Hash flate.NewReader(r io.Reader) io.ReadCloser http.NewFileTransport(fs FileSystem) RoundTripper Regarding `os.File`, the Go team even said: “If we were starting from scratch, we might do it differently.” That’s why Go added abstractions later like fs.FS and fs.File. embed/fs.Open again deliberately breaks this. Whereas consider its counterpart net.Conn. net.Conn is one of the most successful interfaces in the Go standard library. It’s the foundation of the net, net/http, tls, and net/rpc packages, and has been stable since Go 1.0. It didn't need a replacement fs.Fs. If you will always only ever have one implementation in absolute permanence and no mocking/fake/alternative implementation is ever required in eternity, return a concrete type. Otherwise, consider whether returning an interface makes more sense.
- thomashabets2 1y agoBe prepared to be called a newbie for criticising typed nils. My post https://news.ycombinator.com/item?id=44982491 https://news.ycombinator.com/item?id=44982491 got a lot of hate from people who defend Go by saying "so just don't do that!", and people trying to explain my own blog post to me.
- porridgeraisin 1y agoDid not know about index-based string interpolation. Useful! The part about changing a map while iterating is wrong though. The reason you may or may not get it is because go iterates in intentionally random order. It's nothing to do with speed. It's to prevent users from depending on the iteration order. It randomly chooses starting bucket and then goes in circular order, as well as randomly generates a perm of 0..7 inside each bucket. So if your edit goes into a bucket or a slot already visited then it won't be there. Also, python is not an example to the contrary. Modifying python dicts while iterating is a `RuntimeError: dictionary changed size during iteration`
- valzam 1y agoGreat list of why one can love and hate Go. I really did enjoy writing it but you never get the sense that you can be truly certain your code is robust because of subtle behaviour around nil.
- valzam 1y agoI 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.
- theshrike79 1y agoAnd 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 line 42, but it does two dozen things on the single line...
- spoiler 1y agoI know it's not exclusive to Go or any language, but you can most certainly write incomprehensible code in it. If anything, expressiveness and proper abstractions can save you from this. I think people often get burnt by bad abstractions in expressive languages, but it's not a problem of the language, but the author's unfamiliarity with the tools at their disposal. If someone starts being clever with abstractions before understanding the fundamentals, it can lead to badly designed abstractions. So I guess if there's less things to master, you can start designing good abstractions sooner. So, in my experience, if we invest time to truly understand the tools at our disposal, expressive languages tend to be a great boon to comprehension and maintenance. But yes, there's definitely been times early in my career where I abstracted before I understood, or had to deal with other bad abstractions
- ignoramous 1y agoThe time.After function creates a channel that will be sent a message after x seconds. Or, will it? https://github.com/golang/go/issues/24595 https://github.com/golang/go/issues/24595 ... even though the value is nil, the type of the variable is a non-nil interface... Go "boxes" that value in an interface, which is not nil. This can really bite you if you return interfaces from functions Bit me when I was noob. These days, I fail build if ireturn fails. go install github.com/butuzov/ireturn/cmd/ireturn@latest ireturn ./... Go 1.25 introduced a waitgroup.Go function that lets you add Go routines to a waitgroup more easily. sync.WaitGroup(n) panics if Add(x) is called after a Done(-1) & n isn't now zero. Unsure if WaitGroups and easy belong in the same sentence. May be they do, but I'd rather reimplement Java's CountDownLatch & CyclicBarrier APIs in Go instead. When you embed structs, you also implicitly promote any methods they contain ... Say, for instance, you embed a time.Time struct onto a JSON response field and try to marshal that parent ... Since the time.Time method has a MarshalJSON() method, the compiler will run that over the regular marshalling behavior #£@&+!
- dontlaugh 1y agowaitgroup's inadequacy is why I end up using structured concurrency like https://github.com/sourcegraph/conc https://github.com/sourcegraph/conc
- pcthrowaway 1y agoThe last release of this was 2.5 years ago, and it's pre-1.0... is this really ready for production use? Genuinely asking, I'm relatively new to Golang and would love to have a better sense of what parts of the ecosystem are worth learning about.
- hellcow 1y agoI used it in production for lots of systems, so yes. It’s pretty great! That said 2.5 years later there’s been many improvements to the stdlib (like waitGroup.Go) such that I no longer feel the need for it going forward.
- DarkNova6 1y agoAs 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“.
- OvervCW 1y agoI'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.
- eptcyka 1y agoHow is the boxed interface problem enabling better ergonomics for Go? I find that most quirks listed here are not making the language any better.
- OvervCW 1y agoIn this specific blog post I suppose only "Ranging Directly over Integers" counts, but I was more generally referring to the introduction of features like generics.
- 0x696C6961 1y agoIf you think Go and C are that similar then you don't know either.
- quietbritishjim 1y agoGo 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.
- voidUpdate 1y ago> This is helpful if you have to interpolate the same value multiple times and want to reduce repetition and make the interpolation easier to follow. Is index-based string interpolation easier to follow? I would find it easier to understand a string interpolation when the variable name is right there, rather than having to count along the arguments to find the particular one it's referencing
- formerly_proven 1y ago> This is different than, for instance, python, which has a “stable insertion order” that guarantees that this won’t happen. The reason Go does this: speed! In Python you'll actually get a RuntimeError here, because Python detects that you're modifying the dictionary while iterating over it.
- deleted 1y ago[deleted]
- username223 1y agoGo has certainly come a long ways from its initial mission to be a simple language for Rob Pike's simple coworkers. type User struct { Name string `json:"name"` Password string `json:"-"` Email string `json:"email"` } So you can specify how to serialize a struct in json using raw string literals containing arbitrary metadata. And json:"X" means to serialize it to X, except the special value "-" means "omit this one," except "-," means that its name is "-". Got it.
- Cthulhu_ 1y agoI never liked the concept of struct tags, it's a kind of stringly typed programming where the meaning of X or - depends entirely on what the json package says it means. An alternative is to introduce something like annotations, but I'm sure there will be resistance as it makes the language lean closer to e.g. Java. But my take on that is that if you want stricter typing like that, you should actually go to Java or C# or whatever.
- bpicolo 1y agoJava resisted first party support of annotations. It was a very controversial addition in the early 2000s Support for the types of metaprogramming/metadata that annotations are used for is a useful attribute of languages in general
- username223 1y ago"Stringly typed programming" is the phrase I was looking for. It's the equivalent of a shrug from the programming language designer: "I don't know what to do about that, so I'll just add a magic string and let someone else figure it out." That and one or two other examples in the article smelled vaguely of PHP to me: features piled up in response to immediate needs instead of coherent design. For a language that famously refused to add generics for years (then did them badly, IMHO), it seems off-brand.
- rowanseymour 1y agoOf all the things one might critique Go for as not being simple, I'm not sure this is it. I've never needed to serialize "-" as a key in JSON but `-,` makes some sense given the general pattern of field tags, e.g. `json:"name,omitempty"`
- acatton 1y ago> The wg.Go Function > Go 1.25 introduced a waitgroup.Go function that lets you add Go routines to a waitgroup more easily. It takes the place of using the go keyword, [...] 99% of the time, you don't want to use sync.WaitGroup, but rather errgroup.Group. This is basically sync.WaitGroup with error handling. It also has optional context/cancellation support. See https://pkg.go.dev/golang.org/x/sync/errgroup https://pkg.go.dev/golang.org/x/sync/errgroup I know it's not part of the standard library, but it's part of the http://golang.org/x/ http://golang.org/x/ packages. TBH, golang.org/x/ is stuff that should be in the standard library but isn't, for some reason.
- blixt 1y agoI thought exactly the same thing. I use errgroup in practically every Go project because it does something you'd most likely do by hand otherwise, and it does it cleaner. I discovered it after I had already written my own utility to do exactly the same thing, and the code was almost line for line the same, which was pretty funny. But it was a great opportunity to delete some code from the repo without having to refactor anything!
- giancarlostoro 1y ago> and the code was almost line for line the same, which was pretty funny. One of the core strengths of Go is that it fits the zen of Python's " There should be one-- and preferably only one --obvious way to do it" and it does this very nicely.
- lagniappe 1y ago>golang.org/x/ is stuff that should be in the standard library but isn't, for some reason think of it as testing/staging before being merged into stable stdlib
- linhns 1y agoI do believe it’s backwards compatibility and evolving APIs
- 1y ago
- ale42 1y agoDid anyone else read "Go subtitles" instead of the actual title?
- jasonthorsness 1y agoGreat 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() }.
- mwsherman 1y agoIn Go, string effectively serves as a read-only slice, if we are talking about bytes. ReadOnlySpan<T> in C# is great! In my opinion, Go essentially designed in “span” from the start.
- jasonthorsness 1y agoYeah I think the C# team was definitely influenced by Go with their addition of Spans.. Interesting approach regarding using strings as containers for raw bytes, but when you create one over a []byte I believe it makes a copy almost always (always?) so you can’t get a zero-cost read-only view of the data to pass to other functions.
- mwsherman 1y agoThat’s true, converting in either direction will typically allocate. Which it must, semantically. One can use unsafe for a zero-copy conversion, but now you are breaking the semantics: a string becomes mutable, because its underlying bytes are mutable. Or! One can often handle strings and bytes interchangeably with generics: https://github.com/clipperhouse/stringish https://github.com/clipperhouse/stringish
- pjmlp 1y agoLike everything else in Go, spans predate it for a few decades.
- pjmlp 1y agoSystem languages from 1980's already had the span concept. One way that you will find it is that they used to be called open arrays in some of them.
- liampulles 1y agoGo's subtle footguns are definitely its worst aspect. I say that as a "Go fanboy" (I confess). But I think its also worth asking WHY many of these footguns continue to exist from early Go versions - and the answer is that Go takes versioning very seriously and sticking to major version 1 very seriously. The upshot of this dogmatism is that its comparatively easy to dev on long-lived Go projects. If I join a new team with an old Go project, there's a very good chance that I'll be able to load it up in my IDE and get all of Go's excellent LSP, debug, linting, testing, etc. tooling going immediately. And when I start reading the code, its likely not going to look very different from a new Go project I'd start up today. (BTW Thanks OP for these subtleties, there were a few things I learned about).
- Groxx 1y ago>As an additional complexity, although string literals are UTF-8 encoded, they are just aribtrary collections of bytes, which means you can technically have strings that have invalid data in them. In this case, Go replaces invalid UTF-8 data with replacement characters. No, it's just doing the usual "replace unprintable characters when printing" behavior. The data is unchanged, you have no guarantees of UTF-8 validity at all: https://go.dev/play/p/IpYjcMqtmP0 https://go.dev/play/p/IpYjcMqtmP0
- bmn__ 1y agoFTA: > Runes correspond to code points in Go, which are between 1 and 4 bytes long. That's the dumbest thing I've read in this month. Why did they use the wrong word, sowing confusion¹, when any other programming language and the Unicode standard uses the correct expression "code point"? ¹ https://codepoints.net/runic https://codepoints.net/runic already exists
- debugnik 1y ago> uses the correct expression "code point" Actually no, these are Unicode scalars, not code points; they exclude the surrogate category. I agree that rune is a very poor name for it. It both mistakes what runes actually are and clashes with the runic block. But C# has adopted the Rune name for some reason. Rust simply calls these char, and OCaml uchar (unicode char), which are much better choices.
- commandersaki 1y agoSeeing as two of the authors designed utf8 (or at least concurrent to others), I think it’s safe to defer to their expertise and nomenclature here.
- bmn__ 1y agohttp://enwp.org/Appeal_to_accomplishment http://enwp.org/Appeal_to_accomplishment Your use of the fallacy falls short of the reasoning standard expected here on HN. I did not downvote you, because I'd rather engage with words and effect change, but it does not surprise me that someone else did.
- mwsherman 1y agoThere is mention of how len() is bytes, not “characters”. A further subtlety: a rune (codepoint) is still not necessarily a “character” in terms of what is displayed for users — that would be a “grapheme”. A grapheme can be multiple codepoints, with modifiers, joiners, etc. This is true in all languages, it’s a Unicode thing, not a Go thing. Shameless plug, here is a grapheme tokenizer for Go: https://github.com/clipperhouse/uax29/tree/master/graphemes https://github.com/clipperhouse/uax29/tree/master/graphemes
- HeyImAlex 1y agoHere’s my favorite post on the subject https://adam-p.ca/blog/2025/04/string-length/ https://adam-p.ca/blog/2025/04/string-length/
- debugnik 1y agoFinally an article that doesn't pretend grapheme clusters are the be-all end-all of Unicode handling. I'm saving this one. Not exactly how I'd explain it, but it's simplified enough to share with my current co-workers without being misleading.
- virtualritz 1y agolen() is also returning int instead of uint/uint64 in Go. I do not use Go but ran into this when I had to write a Go wrapper for some Rust stuff the other day. I was baffled.
- callc 1y agoI 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.
- lelandbatey 1y agoIt'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 between panics vs errors in go, they feel sooo much clearer than "normal" exception handling (try ... catch) in other languages which syntactically equivocate such common cases as "this file doesn't exist" with "the program is misbehaving due to physical RAM corruption". I think it's great that panic vs errors makes that a brighter line. Assuming recover has to exist, I think forcing it to be in a deferred function is genius because it composes so well with how defers work in go. It's guaranteed to run "when the function returns" which is exactly the time to catch such truly catastrophic behaviors.
- ignoramous 1y agoRecover() 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/internal/logging/panic.go#L36-L64 https://github.com/hashicorp/terraform/blob/325d18262e/inter... [0] which introduced debug.SetCrashOutput: https://pkg.go.dev/runtime/debug#SetCrashOutput https://pkg.go.dev/runtime/debug#SetCrashOutput
- gethly 1y agoSemantics. Go at least does not restrict you to wrap every single panicky call. func Foo() { try { maybePanic() } catch (err any) { doSomething(err) } .. more code } vs func Foo() { defer func() { if err := recover(); err != nil { doSomething(err) } }() maybePanic() .. more code }
- furyofantares 1y agoI 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 map contains your updates. That's totally wrong, right? It makes it sound magical. There's a light explainer but I think it would be a lot more clear to say that of course the update is made immediately, but the "range" iterator may not see it.
- lelandbatey 1y agoIndeed, I have always heard such techniques as "string formatting" while built-in-to-the-language local-variable implicit string formatting sugar syntax is the thing I've heard called "string interpolation". In Python, calling "{}".format(x) is string formatting, while string interpolation would be to use the language feature of "f-strings" such as f"{x}" to do the same thing. As far as I know, go doesn't have string interpolation, it only has convenient string formatting functions via the fmt package. Basically, if you format strings with a language feature: interpolation. If you use a library to format strings: string formatting.
- hnlmorg 1y agoNot quite. Interpolation is where the value is placed directly in the string rather than appended as parameters. Eg “I am $age years old”. This does result in the side effect that interpolation is typically a language feature rather than a library feature. But there’s nothing from preventing someone writing an interpolation library, albeit you’d need a language with decent reflection or a one that’s dynamic from the outset.
- furyofantares 1y agoI think that's usually how it breaks down in practice but even if the language directly provided format strings, I'd still call them format strings, and even if a library provided string interpolation, I'd call it string interpolation. The difference is format strings are a string with indicators that say where to insert values, usually passed as additional arguments, which follow after the string. String interpolation has the arguments inside the string, which says how to pull the values out of the surrounding context.
- tonymet 1y agomy favorite go trick is a simple semaphore using make(chan struct{}, CONCURRENCY) to throttle REST api calls and other concurrent goroutines. It’s really elegant acquisition by reading, and releasing the semaphore by writing. Great to limit your rest / http crawlers to 8 concurrent calls like a web browser.
- sa46 1y agoWhy not use the standard-library adjacent semaphore package? One problem with using a channel as a semaphore is you need to track if you've closed the channel when "releasing". https://pkg.go.dev/golang.org/x/sync/semaphore#Weighted.Acquire https://pkg.go.dev/golang.org/x/sync/semaphore#Weighted.Acqu...
- Someone 1y agoFTA: “In Go, empty structs occupy zero bytes. The Go runtime handles all zero-sized allocations, including empty structs, by returning a single, special memory address that takes up no space. This is why they’re commonly used to signal on channels when you don’t actually have to send any data. Compare this to booleans, which still must occupy some space.” I would expect the compiler to ensure that all references to true and false reference single addresses, too. So, at best, the difference of the more obscure code is to, maybe, gain 8 bytes. What do I overlook?
- Zambyte 1y ago> I would expect the compiler to ensure that all references to true and false reference single addresses, too. Why? If you're sending a constant true to a channel, wouldn't that true value exist in the stack frame for the function call? It seems like that would make more sense than a pointer to a constant true value being stored in the stack frame and dereferencing that every time you need the constant value. > So, at best, the difference of the more obscure code is to, maybe, gain 8 bytes. What do I overlook? Constructing channels in a loop would potentially multiply memory usage here
- arccy 1y agoit's also so that there's no confusion about what the value represents.
- h4ck_th3_pl4n3t 1y agoGo is copy by default. That means it would work if *bool is possible but it's not.
- giancarlostoro 1y agoIf they did it without you explicitly making bool a pointer, then it would be syntactic sugar and it would kind of fall away from the spirit of Go which is, if you look at a file everything that's happening is known to you, there's no metaprogramming witchcraft anywhere in sight usually.
- tczMUFlmoNk 1y ago
- the_mitsuhiko 1y agoOne of the cooler things in Go these days is that the new function based iterators are based on coroutines, and you can use the iter.Pull function to abuse that :)
- fweimer 1y agoIsn't using time.After for timeouts a bit of an anti-pattern? There is no way to cancel the pending computation.
- Philip-J-Fry 1y agoThis was fixed in Go 1.23 https://go.dev/doc/go1.23#timer-changes https://go.dev/doc/go1.23#timer-changes They will get cleaned up.
- fweimer 1y agoI meant that the goroutine in the example keeps running. Often this contradicts the reason for having the timeout in the first place.
- Philip-J-Fry 1y agoI'm not sure what you mean? The examples all return from their function.
- tapirl 1y agoThe wording "Subtleties" used here is some weird/improper. I see nothing subtle here. They are all basic knowledge a qualified Go programmer should know about. They are many real subtleties in Go, which even many professional Go programmers are not aware of. Here are some of them: https://go101.org/blog/2025-10-22-some-real-go-subtleties.html https://go101.org/blog/2025-10-22-some-real-go-subtleties.ht...
- NuclearPM 1y agoThe examples in your link don’t seem to be very useful compared to the subject of this post. “for true {...} and for {...} are not eqivalent” So what? The compiler will tell you the first time you try to run that “for true” abomination that it is invalid code.
- tapirl 1y agoSubtleties are not necessary to be very useful. They, are just real subtleties. :) > > “for true {...} and for {...} are not eqivalent” > So what? The compiler will tell you the first time you try to run that “for true” abomination that it is invalid code. It teaches you know that, when you write func bar() int { for true { ... } return 0 // whatever } You can write it as func bar() int { for { ... } } The compiler will not teach you this. ;D Usefulness might be subjective. Personally, the last two subtleties mentioned in the article are useful for me too. You may find some useful (in your opinion) subtleties in the Go Details and Tips 101 book: https://go101.org/details-and-tips/101.html https://go101.org/details-and-tips/101.html, and some since-Go-1.22/3 ones here: https://go101.org/blog/2024-03-01-for-loop-semantic-changes-in-go-1.22.html https://go101.org/blog/2024-03-01-for-loop-semantic-changes-... and https://go101.org/blog/2025-03-15-some-facts-about-iterators.html https://go101.org/blog/2025-03-15-some-facts-about-iterators...
- radlad 1y ago> Using len() with Strings, and UTF-8 Gotchas Try utf8.RuneCountInString().
- gethly 1y agoProblem with this is that it requires the whole string to be iterated over, byte by byte, or rune by rune, whereas len() does no such thing as the length is stored in the underlying type.
- ball_of_lint 1y agoMy opinion after using go professionally for ~2 years and repeatedly running into gotchas such as https://go.dev/blog/loopvar-preview https://go.dev/blog/loopvar-preview is that it's just not a good language. A lot of people praise it for it's "simplicity" and "explicitness" but frankly, even just figuring out whether something is being passed by reference or value is often complicated. If you're writing code where you never care about that, sure. But for any real project it's not actually better or simpler than C++ or Python.