The comment from mkopriva is correct and should probably be the accepted answer.
However, reading OP's question, I think there might be one potential misunderstanding worth expanding on: OP's mention that the only difference is "one has to be initialized, the other doesn't".
The fundamental difference before a pointer to T
and T
implies a series of behavior changes when using the variable, only one of which being its instantiation.
Firstly: the actual instantiation exists in both cases. In the T
case it's implicit in the declaration, because the variable contains the instance itself (so to speak). But in the pointer case, it may happen at a totally different place in the code, since the variable contains only an indirection to the instance. This explains, among other things, why only the pointer variant can lead to "nil pointer dereference": only in this case can your code attempt to do anything with the variable before it was actually initialized.
Secondly: using a concrete T
together with the fact that go is a "pass by value" language, means any concrete function arguments (or method receivers) are copied for each call.
This has consequences in at least three areas:
- performance: if the instances being copied are big
struct
|s, and the calls happen in the hot path of you application, you will be copying significant amounts of data around.
- memory usage: similarly to the point above: if you have long-running goroutines that receive large
struct
|s, these copies might have an impact on your app's memory footprint.
- semantics: this is arguably the most important difference: if your type has methods that should modify its contents, then you pretty much have to use pointer receivers. Otherwise the method would act on a copy and the changes would be invisible. The corollary is equally important: if you want to signal that your methods will not modify their receiver, using a concrete type (probably with unexported contents) is a good way to achieve that.
Finally, this leads us to the concrete case of sync.Mutex
: looking at the points above and the code, we can see that performance and memory usage are usually not an issue, because sync.Mutex
is a pretty small struct
.
However, the last point is pretty important: what does it mean to have a pointer to a sync.Mutex
? It means a copy of the containing struct
will point to the same lock. I.e.: it means two instances of your struct
might share a lock.
Since go vet
will not complain about copying pointers to mutexes, copying your parent struct
will raise no alarm bells and you might end up protecting two separate instances with the same lock, potentially leading to deadlocks.
In summary: unless you know you want to protect different copies of something with the same lock (IMHO somewhat unlikely), you're better off using concrete sync.Mutex
|es.
If the only reason for making a sync.Mutex
pointer is because go vet
told you not to copy it, then you should probably consider looking one layer up at the struct
you are trying to protect: most likely you're copying it unintentionally by having a concrete receiver like
func (t T) foo(){...}
where you should have
func (t *T) foo(){...}