When you put a sync.Mutex in a Go struct, do you make it a pointer? Leave it as a value? Or just do whatever works at the moment?
That question came back to me after I received a PR suggesting a change in this area. It seemed small at first, but the more I thought about it, the more it felt worth digging into.


My instinct from the beginning was simple: I have generally not used a pointer for Mutex.
There were a few reasons behind that instinct:
- I had long carried the impression that declaring a lock as a non-pointer was basically a coding convention in Go.
- In a lot of Go code I had read, I rarely saw mutexes declared as pointers.
- But an instinct is not an explanation. If someone raises the question in a PR, "it just feels right to me" is not a satisfying answer.
So the real issue became: what is the actual reasoning here?
The key point: sync.Mutex must not be copied
This part is important and well known: sync.Mutex cannot be copied after use.
That naturally leads to the next question:
If a mutex must not be copied, should we declare it as a pointer to avoid accidental copying?
That sounds reasonable at first. But it is worth testing instead of relying on intuition.
Here is a simple example:
```package main import ( "fmt" "sync" ) type Config1 struct { sync.Mutex Name string } type Config2 struct { *sync.Mutex Name string } func main() { c1 := Config1{Name: "1"} cc1 := c1 fmt.Println(cc1.Name) cc1.Lock() cc1.Unlock() c2 := Config2{ Mutex: &sync.Mutex{}, Name: "2", } cc2 := c2 fmt.Println(cc2.Name) cc2.Lock() cc2.Unlock() }
This runs.
But there is one immediate practical difference: if you use a pointer, you must initialize it. Otherwise you will hit a nil pointer problem.
So from a quick runtime test, both forms seem usable. That can make the pointer version look harmless.
But then `go vet` becomes very relevant.

As expected, `go vet` reports a problem because a copy occurred.
And this is where my conclusion goes in the opposite direction from what some people might expect.
## My take: a mutex should usually not be declared as a pointer
I do **not** think using a pointer is the safer default here.
### Reason 1: using a pointer can hide the problem from `go vet`
If the mutex is stored as a pointer, `go vet` will not complain in the same way when the surrounding struct is copied.
But that does not mean the design is sound. It only means the copy is now easier to miss.
In other words, with a pointer field, you may end up "copying" the lock relationship without realizing it. The struct gets copied, but both copies still refer to the same mutex. That is not necessarily what you intended, and the tooling becomes less helpful in catching it.
### Reason 2: copying an object that contains a lock is already suspicious
Why do we use a lock in the first place? Usually because we have some object, state, or operation whose concurrent access needs to be controlled.
That often implies the object has a single identity that owns its own synchronization.
Now think about when you copy that object. If you are truly copying the object itself, then it is no longer acting like a single unique instance.
Take this pattern:
```c2 := Config2{
Mutex: &sync.Mutex{},
Name: "2",
}
cc2 := c2
This is already strange.
You copied the struct, but both values still share the same lock.
That raises a design question immediately:
- Do you want
cc2being locked to also meanc2is locked? - If yes, then the lock probably should not be an internal field of each copied object in the first place.
- Or do you want
cc2andc2to be independently lockable? - If yes, then using a pointer mutex is clearly wrong. In that case, you should not copy the lock relationship at all. You should create a new object and copy only the other data fields as needed.
So in either direction, the pointer version tends to blur intent.
Why I still prefer the non-pointer form
Declaring the mutex directly in the struct makes the ownership model clearer.
It also makes accidental copying easier to detect with tools like go vet. That is valuable. If a struct containing a mutex gets copied, I would rather have that stand out as a problem than quietly pass by because the mutex happened to be behind a pointer.
So my conclusion is straightforward: I would not declare sync.Mutex as a pointer in normal code.
Using a pointer makes accidental misuse easier, not harder.
If the concern is accidental lock copying, the better answer is static analysis. go vet can help, and many Go linters and analysis tools also catch this kind of issue.
Of course, this is still a judgment call rather than a syntax rule. The language allows both forms, and there may be unusual cases where a pointer mutex is genuinely useful. But as a default practice, I do not think it is the right choice.
A small PR question ended up being a good reminder: when something feels off in Go, it is worth tracing the reason all the way down instead of stopping at habit or convention.
