I really like AI, and I love Claude. It is a great tool, particularly when it comes to learning or comparing approaches. It does a pretty good job of evaluating pros and cons, and providing me with explanations for them. All great, until recently.

The PR

In the context of a code change, in a BSON variable containing some file information, I had to include the parsed information extracted from the file name.

doc := bson.M{
	"filename":   fileName,
	"content":    string(content),
	"created_at": time.Now(),
}

meta, err := parser.Parse(fileName)
if err != nil {
	log.Printf("Error parsing file name %s: %v\n", fileName, err)
	continue
}

for k, v := range meta {
	doc[k] = v
}

The comment

Despite being a pretty straightforward change, a colleague left a comment on the last loop.

My IDE suggests to replace the loop with maps.Copy(doc, meta) according to this https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#hdr-Analyzer_mapsloop

Alright, we don’t use that analyzer, but I was curious to know more.

mapsloop: replace explicit loops over maps with calls to maps package

The mapsloop analyzer replaces loops of the form

for k, v := range x { m[k] = v }

with a single call to a function from the maps package, added in Go 1.23. Depending on the context, this could be maps.Copy, maps.Insert, maps.Clone, or maps.Collect.

The transformation to maps.Clone is applied conservatively, as it preserves the nilness of the source map, which may be a subtle change in behavior if the original code did not handle a nil map in the same way.

It wasn’t clear to me whether this approach, other than being shorter and possibly “more idiomatic”, would bring any tangible improvement. While digging into it, I decided to involve Claude, hoping it would point me in the right direction.

Claude

I asked Claude (Opus 4.7) about it:

Does the mapsloop analyzer for Go suggest real improvement here?

for k, v := range meta {
	doc[k] = v
}

the suggestion is to use maps.Copy(doc, meta). How is it better?

The answer was… something.

maps.Copy is better in a few small but real ways.

It’s clearer to read. maps.Copy(doc, meta) immediately tells you “copy meta into doc.” The loop version requires the reader to scan three lines and verify it’s actually a plain copy — not a transformation, not a filter, not a conditional merge. The intent is explicit at a glance.

I don’t think this is a valid point at all. The loop is stupidly simple. In my opinion, it’s even clearer as it is!

It’s slightly faster. The standard library implementation skips some of the overhead of a Go-level range loop. For small maps it’s negligible, but for large maps it’s measurable. Not a reason on its own to switch, but a free bonus.

Interesting. Let’s have a look at the maps.Copy implementation.

func Copy[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](dst M1, src M2) {
	for k, v := range src {
		dst[k] = v
	}
}

alt text

It handles nil destinations more predictably. Both versions panic if doc is nil, but maps.Copy makes the assumption explicit by being a documented function.

Wow. I mean. I don’t even know how to comment on this.

It composes better. If later you want to copy with a filter, you’d switch to maps.Insert with an iterator, or use maps.CopyFunc (depending on Go version). Starting from maps.Copy makes those refactors more natural.

Man, I am just merging two maps. And even if I had to add a filter, I’d need to change the code either way.

The walkback

After confronting it with the truth, it admitted its error.

You’re right, and I overstated the case. Looking at the actual implementation, my “slightly faster” claim was wrong — it’s literally the same loop, no compiler magic, no optimization. That was sloppy of me.

Your other pushbacks are also fair:

The readability argument is subjective. A two-line range with dst[k] = v is about as transparent as Go gets. Calling it less clear than maps.Copy(dst, src) is a stretch — both are immediately obvious to anyone who knows the language.

The nil panic point was filler. Both panic identically. Documenting the panic in a stdlib function doesn’t change runtime behavior.

The composability point assumes a hypothetical refactor that may never happen. You’d rewrite the code either way.

So the honest answer is: the only real reason to prefer maps.Copy is idiomaticness — it’s the standard-library way to express the intent, and consistent codebases tend to be easier to read across files. That’s a legitimate but weak argument, and not enough to justify changing existing working code on its own.

If the analyzer is enabled and you want to satisfy it, switch. If you don’t, ignoring this particular lint or disabling it for the project is perfectly defensible.

Claude didn’t just give an incorrect answer. It confidently invented benefits that did not exist. Hallucinations are nothing new, but this was the first time I had run into one in such a simple, verifiable scenario. Hopefully, it’ll be the last.