Go version

go version go1.23.0 linux/amd64

Output of go env in your module/workspace:

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

What did you do?

tl;dr: This is a plea for a documentation touch-up around big.Int shallow copies. It is possible that a link to this issue may suffice. Here is the scenario:

I was working a case in which a complex structure gnarly needed an additional i big.Int. I chose a big.Int value rather than a big.Int pointer to avoid the need to populate a pointer on every allocation.

There was one case in which that structure was copied, so I created a copy method. Based on the existing documentation, I thought the following would suffice:, and remain valid if there were further changes to gnarly.

func (z *gnarly) Set(x *gnarly) *gnarly {
    *z = *x // creates un-supported shallow copy z.i
    z.i.Set(&x.i) // replace un-supported shallow copy z.i
    return z
}

What did you see happen?

The result was that z.i and x.i were still entangled, sharing the same underlying nat slice. OK, that's fair, shallow copies are unsupported, but the documentation suggests using Int.Set in like cases, and even the Int.Set code suggests that a new value slice will be created when it calls nat.set - quoting from big source:

func (z nat) set(x nat) nat {
    z = z.make(len(x))
    copy(z, x)
    return z
}

But sadly the z.make(len(x)) doesn't make a new slice in this case, it reuses the existing (entangled) one. Reasonable optimization, but dang.

What did you expect to see?

So the plea is this: In cases like this, when refactoring complex existing go code, it may come to be that a shallow big.Int copy is created. What to do about it? The rule violated, game over approach isn't... helpful. What would be helpful is additional documentation, perhaps even a link to this issue, that shows a workaround, such as:

func (z *gnarly) Set(x *gnarly) *gnarly {
    *z = *x // creates un-supported shallow copy z.i
    *z.i = big.Int{} // Blow away unsupported shallow copy
    z.i.Set(&x.i) // populate value.
    return z
}

I admit a typed the example code by hand, there is probably at least one typo, hopefully I can edit.

Comment From: gabyhelp

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: seankhliao

Duplicate of #28423