Preventing Copy Catastrophes: NoCopy in Go
Nothing ever goes away until it has taught us what we need to know.
— Pema Chödrön
Medium Link: noCopy Strategies You Should Know in Golang | by Wesley Wei | Sep, 2024 | Programmer’s Career
Author:Wesley Wei – Medium
Hello, here is Wesley, Today’s article is about noCopy in Go. Without further ado, let’s get started.💪
1.1 Sync.noCopy
When learning about WaitGroup code in Go, I noticed the noCopy and saw a familiar comment: “must not be copied after first use”.
1 | // A WaitGroup must not be copied after first use. |
Through searching, I found that “must not be copied after first use” and the noCopy often appear together.
1 | // Note that it must not be embedded, due to the Lock and Unlock methods. |
By observing the definition of noCopy Definition in Go 1.23, I found:
- The noCopy type is an empty struct.
- The noCopy type implements two methods: Lock and Unlock, which are both no-op methods.
- The comment emphasizes that Lock and Unlock are used by the go vet checker.
The noCopy type has no practical functional properties, and I think it’s only through thinking and experimenting that one can understand its specific purpose, and why “must not be copied after first use”?
1.2 Go Vet and “Locks Erroneously Passed by Value”
When we input the following command:
1 | go tool vet help copylocks |
output:
1 | copylocks: check for locks erroneously passed by value |
We get a message from Go Vet, which tells us that when using values containing locks (such as sync.Mutex
or sync.WaitGroup
) and passing them by value), it may cause unexpected problems. For example:
1 | package main |
If we run this code, it will output an error message:
1 | // output |
The reason for this error is that the Lock
and Unlock
methods use a value receiver t
, which creates a copy of T
when calling the method. This means that the lock instance in Unlock
does not match the one in Lock
.
To fix this, we can change the receiver to a pointer type:
1 | func (t *T) Lock() { |
Similarly, when using Cond
, WaitGroup
and other types that contain locks, we need to ensure that they are not copied after the first use. For example:
1 | package main |
If we run this code, it will also output an error message:
1 | ///// |
To fix this, we can use the same wg
instance, I encourage you to try it. More information about copylocks: copylock.
1.3 Trying out go vet detection
The design of go vet
‘s noCopy
mechanism is a way to prevent structures from being copied, especially for those that contain synchronization primitives (such as sync.Mutex
and sync.WaitGroup
). The purpose is to prevent unexpected copylocks from occurring, but this prevention is not mandatory; whether or not to copy needs to be detected by the developer. For example:
1 | package main |
The above example doesn’t have any practical use; the program can run correctly but go vet
will prompt a “passes lock by value” warning. This is just an exercise to try out the detection mechanism of go vet
.
However, if you need to write code related to synchronization primitives (such as sync.Mutex
and sync.WaitGroup
), the noCopy
mechanism may be useful for you.
1.4 Other noCopy Strategies
From what we’ve learned, go vet
can detect potential copy issues that are not strictly prohibited. Are there any strategies that strictly prohibit copying? Yes, there are. Let’s take a look at the source code for strings.Builder
:
1 | // A Builder is used to efficiently build a string using [Builder.Write] methods. |
The key point is:
1 | b.addr = (*Builder)(abi.NoEscape(unsafe.Pointer(b))) |
This line of code does the following:
unsafe.Pointer(b)
: convertsb
to anunsafe.Pointer
, so that it can be used withabi.NoEscape
.abi.NoEscape(unsafe.Pointer(b))
: tells the compiler thatb
will not escape, i.e., it can continue to be allocated on the stack instead of the heap.(*Builder)(…)
: converts theabi.NoEscape
return value back to a*Builder
type, so that it can be used normally.- Finally,
b.addr
is set to the address ofb
itself, which preventsBuilder
from being copied (with a check forb.addr != b
in the following logic).
go1.23.0 builder.go
abi.NoEscape
Using strings.Builder
with copying behavior will cause a panic:
1 | func main() { |
Another example is sync.Cond
, which can be referred to in the article “What does “nocopy after first use” mean in golang and how | by Jing | Medium“.
1.5 Summary
- Synchronization primitives (such as
sync.Mutex
andsync.WaitGroup
) should not be copied, because once they are copied, their internal state will be duplicated, leading to concurrency issues. - Although Go itself does not provide a mechanism to strictly prevent copying, the
noCopy
struct provides a non-strict mechanism for identifying and detecting copies using thego vet
tool. - Some source code in Go performs no-copy checks at runtime and returns a panic, such as
strings.Builder
andsync.Cond
.
References
Detect locks passed by value in Go | by Michał Łowicki | golangspec | Medium
What does “nocopy after first use” mean in golang and how | by Jing | Medium
More Series Articles about You Should Know In Golang:
https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a
And I’m Wesley, delighted to share knowledge from the world of programming.
Don’t forget to follow me for more informative content, or feel free to share this with others who may also find it beneficial. it would be a great help to me.
Give me some free applauds, highlights, or replies, and I’ll pay attention to those reactions, which will determine whether or not I continue to post this type of article.
See you in the next article. 👋
中文文章: https://programmerscareer.com/zh-cn/golang-nocopy/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: Originally written at https://programmerscareer.com/golang-nocopy/ at 2024-09-07 00:19. If you choose to repost or use this article, please cite the original source.
Comments