What does “nocopy after first use” mean in golang and how

Jing
4 min readMar 31, 2019

--

When we read golang source code or learn to use some builtin structs, frequently we may be told that “must not be copied after first use”, such as sync.Cond, sync.Map, sync.Mutex (nearly all types in sync package) and strings.Builder. Most of the time it is required so for safety reasons, for example you have a struct with a pointer field and you don’t want it to be copied since a shallow copy will make these two hold the same pointer and be unsafe. So what does golang do to ensure that? There isn’t a perfect answer as far as I know (see discussions here), but in this story, I will review the following two solutions and hope it provide a better understanding.

1. runtime checking

This is done by encapsulating a pointer to itself and check before any further operation. A typical example is strings.Builder :

type Builder struct {
addr *Builder
buf []byte
}
func (b *Builder) copyCheck() {
if b.addr == nil {
b.addr = (*Builder)(noescape(unsafe.Pointer(b)))
} else if b.addr != b {
panic("strings: illegal use of non-zero Builder copied by value")
}
}
func (b *Builder) Write(p []byte) (int, error) {
b.copyCheck()
...
}
// test case
var a strings.Builder
a.Write([]byte("testa"))
var b = a
b.Write([]byte("testb")) // will panic here

As you can see, when we declare builder a and write, a.addr will be assigned its own address. After we assign a to b, a.addr will be shallowly copied to b.addr, but the newly allocated address of b is certainly not the same as a.addr, so panic happens. This solution utilizes the fact that pointer is shallowly copied and easy to understand.

Another example is sync.Cond:

type Cond struct {
noCopy noCopy
L Locker
notify notifyList
checker copyChecker
}
type copyChecker uintptrfunc (c *copyChecker) check() {
if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
uintptr(*c) != uintptr(unsafe.Pointer(c)) {
panic("sync.Cond is copied")
}
}
func (c *Cond) Wait() {
c.checker.check()
...
}

It is a bit difficult to read the check() function at first sight, so let’s define a similar struct and try to find out why:

type cond struct {
checker copyChecker
}
type copyChecker uintptrfunc (c *copyChecker) check() {
fmt.Printf("Before: c: %v, *c: %v, uintptr(*c): %v, uintptr(unsafe.Pointer(c)): %v\n", c, *c, uintptr(*c), uintptr(unsafe.Pointer(c)))
atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c)))
fmt.Printf("After: c: %v, *c: %v, uintptr(*c): %v, uintptr(unsafe.Pointer(c)): %v\n", c, *c, uintptr(*c), uintptr(unsafe.Pointer(c)))
}
// test case
var a cond
a.checker.check()
b := a
b.checker.check()
// results
Before: c: 0x414020, *c: 0, uintptr(*c): 0, uintptr(unsafe.Pointer(c)): 4276256
After: c: 0x414020, *c: 4276256, uintptr(*c): 4276256, uintptr(unsafe.Pointer(c)): 4276256
Before: c: 0x414044, *c: 4276256, uintptr(*c): 4276256, uintptr(unsafe.Pointer(c)): 4276292
After: c: 0x414044, *c: 4276256, uintptr(*c): 4276256, uintptr(unsafe.Pointer(c)): 4276292

Clearly when we declare a , its checker field is 0and the address of checker field is 0x414020 or the decimal 4276256 . After CompareAndSwapUintptr() , its checker field is assigned its own address, say 4276256 . When we assign a to b , the checker field of a is copied to that of b , but the address of b’s checker field is actually 0x414044 or the decimal 4276292. So it finally meets all three conditions(in fact two) and detects copy. This is still a “self pointer” approach and I guess you got it.

To summarize, runtime checking often uses a self pointer and won’t check until runtime.

2. Go vet copylocks checking

-copylocks is actually a go vet flag which checks whether a locker type is copied or not. A locker type is a type which has Lock() and Unlock() methods. See here for more details. As mentioned before, nearly all types in sync package must not be copied and actually it’s guaranteed just by encapsulating a noCopy struct:

// src/sync/cond.go
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
// sync.Pool
type Pool struct {
noCopy noCopy
...
}
// sync.WaitGroup
type WaitGroup struct {
noCopy noCopy
...
}

Go vet will examine every statement and operand for a locker type and in this way copy could be found before runtime. So if you want a type to be not copied, what you need to do is simply defining a noCopy struct in your package and encapsulating it as extra field like the following:

type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type MyType struct {
noCopy noCopy
...
}

Then go vet will do the check work for you. That’s it.

Conclusion

Though currently we don’t have a perfect answer for this issue, these two approaches are still instructive in similar situations. Self pointer or go vet check? Give it a try next time :)

--

--

Jing
Jing

Written by Jing

Breathtaking interfaces and strong services together make great products

No responses yet