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/.