A generic atomic counter for typed IDs
Since Go 1.19 the sync/atomic package has atomic.Int64, atomic.Uint64, and atomic.Pointer[T]. What it does not have is a generic atomic.Value[T] for a typed integer like type RequestID int64. You can roll one in about 15 lines.
It is not a performance play. The point is type safety: the compiler catches counter.Add(userID) when counter is a Counter[RequestID], which is the kind of mistake that otherwise survives review and causes a very confused production incident.
type Integer interface {
~int | ~int32 | ~int64 | ~uint | ~uint32 | ~uint64
}
type Counter[T Integer] struct {
n atomic.Int64
}
func (c *Counter[T]) Add(delta T) T {
return T(c.n.Add(int64(delta)))
}
func (c *Counter[T]) Load() T {
return T(c.n.Load())
}
func (c *Counter[T]) Store(v T) {
c.n.Store(int64(v))
}
// Usage:
type RequestID int64
var nextReqID Counter[RequestID]
func newRequest() Request {
id := nextReqID.Add(1)
return Request{ID: id} // id is a RequestID, not a bare int64
}
Caveats: the internal storage is always int64, so a Counter[uint64] truncates at math.MaxInt64. For unsigned-only counters, write a separate UCounter[T] backed by atomic.Uint64. See also /posts/generics-in-go-eighteen-months-later/.