Atomic functions or methods?

Channels and mutexes cover most of your code's synchronization needs. You'd rarely have to reach for the sync/atomic package, but if you have to, you may notice the recommendation that comes with each package-level atomic function such as AddInt32:

“Consider using the more ergonomic and less error-prone Int32.Add instead.”

Uh, but why? Both the package-level function and the atomic type's method do exactly the same, right? Right, but they do it in different ways. The documentation says “more ergonomic and less error-prone”. Sounds good, but what does that mean?

Atomic types and their methods are more ergonomic

The case for ergonomics is clear to see if you compare the two approaches directly:

// Package-level function (less ergonomic)
var counter int32
atomic.AddInt32(&counter, 1)

// Type-specific method (more ergonomic)
var counter atomic.Int32
counter.Add(1)

The method is, very obviously, shorter to type, hence less wordy. For the package-level function, you have to repeat the counter's type in the function name and have to remember that the counter parameter is a pointer that you must dereference. The method, on the other hand, exposes a clear and brief Add() method.

Atomic types and their methods are less error-prone

This one is less obvious than the ergonomics, but if you look closely, you notice that the “atomic” counter in the package-level function example isn't atomic at all; it's a plain int32. Imagine a new developer on the team who fixes a critical bug in a hurry and increments the counter through counter++. This increment operator isn't atomic, and now your code has a data race condition.

This cannot happen with atomic types like atomic.Int32, as the increment operator isn't defined on them. The only way to increment an atomic Int32 is by calling Add(1).

var counter atomic.Int32

// This won't even compile
counter++

// This works
counter.Add(1)

So: Use the atomic types and their methods instead of the package-level functions. It's not just a matter of taste.