Hash Tables in Go and Advantage of Self-Hosted Compilers
Key topics
A lively debate erupted over the reliability of Large Language Models (LLMs) and the best practices for implementing sets in Go. Some commenters, like Hendrikto, urged extreme skepticism towards LLMs, while others, like nasretdinov, pointed out that humans can be just as untrustworthy. The discussion also touched on the idiomatic use of empty structs in Go, with cabirum and ioanaci arguing that it's a clear and expected way to implement sets, whereas using bools can lead to misuse, as tym0 noted. As the conversation unfolded, it became clear that, despite some disagreements, there's a strong consensus on the importance of verification and the value of idiomatic coding practices.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
6d
Peak period
38
132-144h
Avg / period
14.7
Based on 44 loaded comments
Key moments
- 01Story posted
Dec 14, 2025 at 2:32 PM EST
19 days ago
Step 01 - 02First comment
Dec 20, 2025 at 7:01 AM EST
6d after posting
Step 02 - 03Peak activity
38 comments in 132-144h
Hottest window of the conversation
Step 03 - 04Latest activity
Dec 21, 2025 at 5:52 AM EST
12 days ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
I would go even farther and say to not trust anything they say. Always be skeptical, always verify.
An empty struct is idiomatic and expected to be used in a Set type. When/if the memory optimization is reintroduced, no code change will be needed to take advantage of it.
I would argue using bool hurts readability more.
Even better write/use a simple library that calls things that are sets `Set`.
https://github.com/golang/go/discussions/47331
edit: I may be wrong here
1. How to do sets in Go?
2. What changed between Go 1.24 and 1.25?
3. Trusting an LLM?
4. Self-hosted compilers?
It is not clear at all. Also there are no conclusions, it's basically a guy figuring out for no reason that the way maps are implemented has changed in Go?
And the title is about self-hosted compilers, whose "advantage" turned out to be just that the guy was able to read the code? How is that an advantage? I guess it is an advantage for him.
The TypeScript compiler is also written in Go instead of in TypeScript. So this shouldn't be an advantage? But this guy likes to read Go, so it would also be an advantage to him.
This is an article written by a real human person, who's going to meander a bit. I prefer that over an LLM article which is 100% focused, 100% confident, and 100% wrong. Let's give the human person a little bit of slack.
In the general case then yes, but here you can't take addresses of dictionary values (the compiler won't let you) so adding 1 byte to make a unique pointer for the struct {} shouldn't be necessary.
Unless it is used in the implementation of the map I suppose.
So I conjecture a bit of internal magic could fix this.
There are two reasons, and we could also ask "why can't you get the address of a key in a map?"
The first reason is flexibility in implementation. Maps are fairly opaque, their implementation details are some of the least exposed in the language (see also: channels), and this is done on purpose to discourage users of the language from mucking with the internals and thus making it harder for the developers of the language to change them. Denying access to internal pointers makes it a lot easier to change the implementation of a map.
The second reason is that most ways of implementing a map move the value around copiously. Supposing you could get a pointer p := &m[k] for some map m and key k, what would it even point to? Just the value position of a slot in a hash table. If you do delete(m, k) now what does it point to? If you assign m[k2] but hash(k2) == hash(k) and the map handles the collision by picking a new slot for k, now what does it point to? And eventually you may assign so many keys that the old hash table is too small and so a new one somewhere else in memory has to be allocated, leaving the pointer dangling.
While the above also apply to pointers-to-keys, there is another reason you can't get one of those: if you mutated the key, you would (with high probability) violate the core invariant of a hash table, namely that the slot for an entry is determined exactly by the hash of its key. The exact consequences of violating this would depend on the specific implementation, but they are mostly quite bad.
Rust, with its strong control over mutability and lifetimes, can give you a reference to the key and/or value of an entry in a HashMap, but you won't be able to change the map in safe code while such a reference is alive.
The footgun was that url.QueryUnescape returned a slice of the original string if nothing needed to be escaped so if the original string was modified, it would modify the key in the map if you put the returned slice directly into the map.
My guess is fiber used the same string for the param to avoid allocations.
[1] https://github.com/gofiber/fiber
I see that fiber goes behind your back and produces potentially mutable strings behind your back: https://github.com/gofiber/utils/blob/c338034/convert.go#L18
And I don't have an issue with it to be honest. I do the same myself.
But this mutability must never escape. I'd never persist in using a library that would let it escape; that doesn't treat it as a gross bug. This breaks so many things that it's not even funny.
Hypotheses: you were modifying the map in another goroutine (do not share maps between goroutines unless they all treat it as read-only), the map implementation had some short-circuit logic for strings which was broken (file a bug report/it's probably already fixed), the debugger paused execution at an unsafe location (e.g. in the middle of non-user code), or the debugger incorrectly interpreted the contents of the map.
E.g. the JVM is a C++ project, but you can easily read the HashMap implementation, because it's part of the standard library, not part of the runtime.
Linker relies on the current memory layout. Changing it requires updating linker code as well.
But an implementation change that will for sure baloon the memory usage of everybody's code making heavy use of Hashmap-as-set (a popular idiom)? Yeah no problem, change shipped.
Its frustrating and I say this as someone who has been writing Go for around a decade.
Given GoLangs compatibility guarantee, any mistake in the design of a language API has to be preserved forever, and is very difficult to improve.
But implementations of the GoLang spec and language APIs are much easier to evolve. There’s nothing preventing the Go team rolling out future improvements to deal with this issue, without having to worry about long term consequences. There’s also nothing preventing other implementations of the GoLang spec choosing a different approach.
Boolean on the other hand inherently contains two information: either true or false. ie. there will always be information and it will always be one of two values.
This is similar to *struct{} where we can signal no information, or false, by returning/passing nil or initiated pointer to empty struct as true/value present.
For maps, bool makes more sense as otherwise we just want a list with fast access to determine whether value in the list exists or not. Which is often something we might want. But it should not detract form the fact that each type has its own place and just because new implementation for maps ignores this, in this particular use, case does not make them worse than previous version.
tl;dr it is good to know this fact about the new swiss maps, but it should not have any impact on programming an design decisions whatsoever.