Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected Pointer Behavior in range Loop Over Maps in Go (Go 1.24.1) #73138

Closed
wolfhermann opened this issue Apr 2, 2025 · 3 comments
Closed
Labels
BugReport Issues describing a possible bug in the Go implementation.

Comments

@wolfhermann
Copy link

Go version

1.24.1

Output of go env in your module/workspace:

AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/sa/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/sa/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2569810058=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/sa/dev/core/go.mod'
GOMODCACHE='/home/sa/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/sa/go'
GOPRIVATE=''
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/usr/local/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/sa/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/usr/local/go/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.1'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

I'm encountering unexpected behavior when iterating over a map in Go 1.24.1, where pointers to map values inside a range loop do not behave consistently across different environments. The issue manifests as the pointers either referring to the same object (the last item in the loop) or different items, depending on the platform.

Code Example:

package main

import (
	"fmt"
)

type KeyItem struct {
	ID      int
	Version string
}

type Item struct {
	ID    int
	Value string
}

func main() {
	// Define an array of items
	items := map[KeyItem]Item{
		{ID: 1, Version: "1"}: {ID: 1, Value: "Item 1"},
		{ID: 2, Version: "2"}: {ID: 2, Value: "Item 2"},
		{ID: 3, Version: "3"}: {ID: 3, Value: "Item 3"},
	}

	var recordPointer1 *Item
	var recordPointer2 *Item

	// Add only the first item from the loop to the map
	for key, item := range items {
		fmt.Printf("Currently processing: %v\n", item)
		if key.ID == 1 {
			fmt.Printf("Updating pointer 1 to %v\n", item)
			recordPointer1 = &item
		}
		if key.Version == "2" {
			fmt.Printf("Updating pointer 2 to %v\n", item)
			recordPointer2 = &item
		}
	}

	// Check what the map points to after the loop
	fmt.Printf("Pointer1 content after full iteration: %v\n", *recordPointer1)
	fmt.Printf("Pointer2 content after full iteration: %v\n", *recordPointer2)
}

What did you see happen?

What did you expect to see?

I know that the Go range loop over maps reuses the same variable for each iteration, which can lead to unexpected behavior when taking pointers to the map values. This happens because the memory for item is reused across iterations, causing all pointers to potentially reference the same underlying object (the last one processed).

The issue can be resolved by explicitly creating a copy of the item inside the loop:

itemCopy := item
recordPointer1 = &itemCopy

Nevertheless I am highly surprised that I see different outcome using different or even the same go version (1.24.1) dependent on the actual environments.

Request for Clarification:

  • Is this behavior considered a bug in Go, or is it expected behavior due to how the range loop is designed? Should developers expect this behavior across all Go versions, or is it a side effect of specific compiler or runtime configurations?
  • Even if this behavior is by design, is there any intention to make it clearer in the documentation or to change it in the future? This pattern introduces an implicit risk of misuse, as developers might unintentionally rely on incorrect pointer behavior, leading to subtle bugs that are difficult to diagnose.

Thanks for your support!

@seankhliao
Copy link
Member

that compiler is using go1.20.6, before the loopvar change https://go.dev/blog/loopvar-preview
(you can check through reading runtime/debug.BuildInfo)

Closing as working as intended

Unlike many projects, the Go project does not use GitHub Issues for general discussion or asking questions. GitHub Issues are used for tracking bugs and proposals only.

For questions please refer to https://github.com/golang/go/wiki/Questions

@seankhliao seankhliao closed this as not planned Won't fix, can't repro, duplicate, stale Apr 2, 2025
@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Apr 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation.
Projects
None yet
Development

No branches or pull requests

4 participants