Automatically generate integer and Value wrappers (#73)

Rather than hand-writing the atomic wrappers for integers and types
which wrap `atomic.Value`, generate them automatically from shared
templates. The generators are placed under `internal/gen-*` to avoid
external usage.

A new `make` target verifies that the code checked into the repository
is always up-to-date.
diff --git a/.codecov.yml b/.codecov.yml
index 6d4d1be..571116c 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -13,3 +13,7 @@
         if_not_found: success  # if parent is not found report status as success, error, or failure
         if_ci_failed: error    # if ci fails report status as success, error, or failure
 
+# Also update COVER_IGNORE_PKGS in the Makefile.
+ignore:
+  - /internal/gen-atomicint/
+  - /internal/gen-valuewrapper/
diff --git a/Makefile b/Makefile
index 39af0fb..7203311 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,15 @@
 export GOBIN ?= $(shell pwd)/bin
 
 GOLINT = $(GOBIN)/golint
+GEN_ATOMICINT = $(GOBIN)/gen-atomicint
+GEN_VALUEWRAPPER = $(GOBIN)/gen-valuewrapper
 
-GO_FILES ?= *.go
+GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print)
+
+# Also update ignore section in .codecov.yml.
+COVER_IGNORE_PKGS = \
+	go.uber.org/atomic/internal/gen-atomicint \
+	go.uber.org/atomic/internal/gen-valuewrapper
 
 .PHONY: build
 build:
@@ -22,14 +29,42 @@
 $(GOLINT):
 	go install golang.org/x/lint/golint
 
+$(GEN_VALUEWRAPPER): $(wildcard ./internal/gen-valuewrapper/*)
+	go build -o $@ ./internal/gen-valuewrapper
+
+$(GEN_ATOMICINT): $(wildcard ./internal/gen-atomicint/*)
+	go build -o $@ ./internal/gen-atomicint
+
 .PHONY: golint
 golint: $(GOLINT)
 	$(GOLINT) ./...
 
 .PHONY: lint
-lint: gofmt golint
+lint: gofmt golint generatenodirty
+
+# comma separated list of packages to consider for code coverage.
+COVER_PKG = $(shell \
+	go list -find ./... | \
+	grep -v $(foreach pkg,$(COVER_IGNORE_PKGS),-e "^$(pkg)$$") | \
+	paste -sd, -)
 
 .PHONY: cover
 cover:
-	go test -coverprofile=cover.out -coverpkg ./... -v ./...
+	go test -coverprofile=cover.out -coverpkg  $(COVER_PKG) -v ./...
 	go tool cover -html=cover.out -o cover.html
+
+.PHONY: generate
+generate: $(GEN_ATOMICINT) $(GEN_VALUEWRAPPER)
+	go generate ./...
+
+.PHONY: generatenodirty
+generatenodirty:
+	@[ -z "$$(git status --porcelain)" ] || ( \
+		echo "Working tree is dirty. Commit your changes first."; \
+		exit 1 )
+	@make generate
+	@status=$$(git status --porcelain); \
+		[ -z "$$status" ] || ( \
+		echo "Working tree is dirty after `make generate`:"; \
+		echo "$$status"; \
+		echo "Please ensure that the generated code is up-to-date." )
diff --git a/atomic.go b/atomic.go
index fe17478..0a0bba5 100644
--- a/atomic.go
+++ b/atomic.go
@@ -29,258 +29,6 @@
 	"time"
 )
 
-// Int32 is an atomic wrapper around an int32.
-type Int32 struct{ v int32 }
-
-// NewInt32 creates an Int32.
-func NewInt32(i int32) *Int32 {
-	return &Int32{i}
-}
-
-// Load atomically loads the wrapped value.
-func (i *Int32) Load() int32 {
-	return atomic.LoadInt32(&i.v)
-}
-
-// Add atomically adds to the wrapped int32 and returns the new value.
-func (i *Int32) Add(n int32) int32 {
-	return atomic.AddInt32(&i.v, n)
-}
-
-// Sub atomically subtracts from the wrapped int32 and returns the new value.
-func (i *Int32) Sub(n int32) int32 {
-	return atomic.AddInt32(&i.v, -n)
-}
-
-// Inc atomically increments the wrapped int32 and returns the new value.
-func (i *Int32) Inc() int32 {
-	return i.Add(1)
-}
-
-// Dec atomically decrements the wrapped int32 and returns the new value.
-func (i *Int32) Dec() int32 {
-	return i.Sub(1)
-}
-
-// CAS is an atomic compare-and-swap.
-func (i *Int32) CAS(old, new int32) bool {
-	return atomic.CompareAndSwapInt32(&i.v, old, new)
-}
-
-// Store atomically stores the passed value.
-func (i *Int32) Store(n int32) {
-	atomic.StoreInt32(&i.v, n)
-}
-
-// Swap atomically swaps the wrapped int32 and returns the old value.
-func (i *Int32) Swap(n int32) int32 {
-	return atomic.SwapInt32(&i.v, n)
-}
-
-// MarshalJSON encodes the wrapped int32 into JSON.
-func (i *Int32) MarshalJSON() ([]byte, error) {
-	return json.Marshal(i.Load())
-}
-
-// UnmarshalJSON decodes JSON into the wrapped int32.
-func (i *Int32) UnmarshalJSON(b []byte) error {
-	var v int32
-	if err := json.Unmarshal(b, &v); err != nil {
-		return err
-	}
-	i.Store(v)
-	return nil
-}
-
-// Int64 is an atomic wrapper around an int64.
-type Int64 struct{ v int64 }
-
-// NewInt64 creates an Int64.
-func NewInt64(i int64) *Int64 {
-	return &Int64{i}
-}
-
-// Load atomically loads the wrapped value.
-func (i *Int64) Load() int64 {
-	return atomic.LoadInt64(&i.v)
-}
-
-// Add atomically adds to the wrapped int64 and returns the new value.
-func (i *Int64) Add(n int64) int64 {
-	return atomic.AddInt64(&i.v, n)
-}
-
-// Sub atomically subtracts from the wrapped int64 and returns the new value.
-func (i *Int64) Sub(n int64) int64 {
-	return atomic.AddInt64(&i.v, -n)
-}
-
-// Inc atomically increments the wrapped int64 and returns the new value.
-func (i *Int64) Inc() int64 {
-	return i.Add(1)
-}
-
-// Dec atomically decrements the wrapped int64 and returns the new value.
-func (i *Int64) Dec() int64 {
-	return i.Sub(1)
-}
-
-// CAS is an atomic compare-and-swap.
-func (i *Int64) CAS(old, new int64) bool {
-	return atomic.CompareAndSwapInt64(&i.v, old, new)
-}
-
-// Store atomically stores the passed value.
-func (i *Int64) Store(n int64) {
-	atomic.StoreInt64(&i.v, n)
-}
-
-// Swap atomically swaps the wrapped int64 and returns the old value.
-func (i *Int64) Swap(n int64) int64 {
-	return atomic.SwapInt64(&i.v, n)
-}
-
-// MarshalJSON encodes the wrapped int64 into JSON.
-func (i *Int64) MarshalJSON() ([]byte, error) {
-	return json.Marshal(i.Load())
-}
-
-// UnmarshalJSON decodes JSON into the wrapped int64.
-func (i *Int64) UnmarshalJSON(b []byte) error {
-	var v int64
-	if err := json.Unmarshal(b, &v); err != nil {
-		return err
-	}
-	i.Store(v)
-	return nil
-}
-
-// Uint32 is an atomic wrapper around an uint32.
-type Uint32 struct{ v uint32 }
-
-// NewUint32 creates a Uint32.
-func NewUint32(i uint32) *Uint32 {
-	return &Uint32{i}
-}
-
-// Load atomically loads the wrapped value.
-func (i *Uint32) Load() uint32 {
-	return atomic.LoadUint32(&i.v)
-}
-
-// Add atomically adds to the wrapped uint32 and returns the new value.
-func (i *Uint32) Add(n uint32) uint32 {
-	return atomic.AddUint32(&i.v, n)
-}
-
-// Sub atomically subtracts from the wrapped uint32 and returns the new value.
-func (i *Uint32) Sub(n uint32) uint32 {
-	return atomic.AddUint32(&i.v, ^(n - 1))
-}
-
-// Inc atomically increments the wrapped uint32 and returns the new value.
-func (i *Uint32) Inc() uint32 {
-	return i.Add(1)
-}
-
-// Dec atomically decrements the wrapped int32 and returns the new value.
-func (i *Uint32) Dec() uint32 {
-	return i.Sub(1)
-}
-
-// CAS is an atomic compare-and-swap.
-func (i *Uint32) CAS(old, new uint32) bool {
-	return atomic.CompareAndSwapUint32(&i.v, old, new)
-}
-
-// Store atomically stores the passed value.
-func (i *Uint32) Store(n uint32) {
-	atomic.StoreUint32(&i.v, n)
-}
-
-// Swap atomically swaps the wrapped uint32 and returns the old value.
-func (i *Uint32) Swap(n uint32) uint32 {
-	return atomic.SwapUint32(&i.v, n)
-}
-
-// MarshalJSON encodes the wrapped uint32 into JSON.
-func (i *Uint32) MarshalJSON() ([]byte, error) {
-	return json.Marshal(i.Load())
-}
-
-// UnmarshalJSON decodes JSON into the wrapped uint32.
-func (i *Uint32) UnmarshalJSON(b []byte) error {
-	var v uint32
-	if err := json.Unmarshal(b, &v); err != nil {
-		return err
-	}
-	i.Store(v)
-	return nil
-}
-
-// Uint64 is an atomic wrapper around a uint64.
-type Uint64 struct{ v uint64 }
-
-// NewUint64 creates a Uint64.
-func NewUint64(i uint64) *Uint64 {
-	return &Uint64{i}
-}
-
-// Load atomically loads the wrapped value.
-func (i *Uint64) Load() uint64 {
-	return atomic.LoadUint64(&i.v)
-}
-
-// Add atomically adds to the wrapped uint64 and returns the new value.
-func (i *Uint64) Add(n uint64) uint64 {
-	return atomic.AddUint64(&i.v, n)
-}
-
-// Sub atomically subtracts from the wrapped uint64 and returns the new value.
-func (i *Uint64) Sub(n uint64) uint64 {
-	return atomic.AddUint64(&i.v, ^(n - 1))
-}
-
-// Inc atomically increments the wrapped uint64 and returns the new value.
-func (i *Uint64) Inc() uint64 {
-	return i.Add(1)
-}
-
-// Dec atomically decrements the wrapped uint64 and returns the new value.
-func (i *Uint64) Dec() uint64 {
-	return i.Sub(1)
-}
-
-// CAS is an atomic compare-and-swap.
-func (i *Uint64) CAS(old, new uint64) bool {
-	return atomic.CompareAndSwapUint64(&i.v, old, new)
-}
-
-// Store atomically stores the passed value.
-func (i *Uint64) Store(n uint64) {
-	atomic.StoreUint64(&i.v, n)
-}
-
-// Swap atomically swaps the wrapped uint64 and returns the old value.
-func (i *Uint64) Swap(n uint64) uint64 {
-	return atomic.SwapUint64(&i.v, n)
-}
-
-// MarshalJSON encodes the wrapped uint64 into JSON.
-func (i *Uint64) MarshalJSON() ([]byte, error) {
-	return json.Marshal(i.Load())
-}
-
-// UnmarshalJSON decodes JSON into the wrapped uint64.
-func (i *Uint64) UnmarshalJSON(b []byte) error {
-	var v uint64
-	if err := json.Unmarshal(b, &v); err != nil {
-		return err
-	}
-	i.Store(v)
-	return nil
-}
-
 // Bool is an atomic Boolean.
 type Bool struct{ v uint32 }
 
diff --git a/atomic_test.go b/atomic_test.go
index 41c4ab4..b57d197 100644
--- a/atomic_test.go
+++ b/atomic_test.go
@@ -29,158 +29,6 @@
 	"github.com/stretchr/testify/require"
 )
 
-func TestInt32(t *testing.T) {
-	atom := NewInt32(42)
-
-	require.Equal(t, int32(42), atom.Load(), "Load didn't work.")
-	require.Equal(t, int32(46), atom.Add(4), "Add didn't work.")
-	require.Equal(t, int32(44), atom.Sub(2), "Sub didn't work.")
-	require.Equal(t, int32(45), atom.Inc(), "Inc didn't work.")
-	require.Equal(t, int32(44), atom.Dec(), "Dec didn't work.")
-
-	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
-	require.Equal(t, int32(0), atom.Load(), "CAS didn't set the correct value.")
-
-	require.Equal(t, int32(0), atom.Swap(1), "Swap didn't return the old value.")
-	require.Equal(t, int32(1), atom.Load(), "Swap didn't set the correct value.")
-
-	atom.Store(42)
-	require.Equal(t, int32(42), atom.Load(), "Store didn't set the correct value.")
-
-	t.Run("JSON/Marshal", func(t *testing.T) {
-		bytes, err := json.Marshal(atom)
-		require.NoError(t, err, "json.Marshal errored unexpectedly.")
-		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
-	})
-
-	t.Run("JSON/Unmarshal", func(t *testing.T) {
-		err := json.Unmarshal([]byte("40"), &atom)
-		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
-		require.Equal(t, int32(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
-	})
-
-	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
-		err := json.Unmarshal([]byte(`"40"`), &atom)
-		require.Error(t, err, "json.Unmarshal didn't error as expected.")
-		assertErrorJSONUnmarshalType(t, err,
-			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
-	})
-}
-
-func TestInt64(t *testing.T) {
-	atom := NewInt64(42)
-
-	require.Equal(t, int64(42), atom.Load(), "Load didn't work.")
-	require.Equal(t, int64(46), atom.Add(4), "Add didn't work.")
-	require.Equal(t, int64(44), atom.Sub(2), "Sub didn't work.")
-	require.Equal(t, int64(45), atom.Inc(), "Inc didn't work.")
-	require.Equal(t, int64(44), atom.Dec(), "Dec didn't work.")
-
-	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
-	require.Equal(t, int64(0), atom.Load(), "CAS didn't set the correct value.")
-
-	require.Equal(t, int64(0), atom.Swap(1), "Swap didn't return the old value.")
-	require.Equal(t, int64(1), atom.Load(), "Swap didn't set the correct value.")
-
-	atom.Store(42)
-	require.Equal(t, int64(42), atom.Load(), "Store didn't set the correct value.")
-
-	t.Run("JSON/Marshal", func(t *testing.T) {
-		bytes, err := json.Marshal(atom)
-		require.NoError(t, err, "json.Marshal errored unexpectedly.")
-		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
-	})
-
-	t.Run("JSON/Unmarshal", func(t *testing.T) {
-		err := json.Unmarshal([]byte("40"), &atom)
-		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
-		require.Equal(t, int64(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
-	})
-
-	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
-		err := json.Unmarshal([]byte(`"40"`), &atom)
-		require.Error(t, err, "json.Unmarshal didn't error as expected.")
-		assertErrorJSONUnmarshalType(t, err,
-			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
-	})
-}
-
-func TestUint32(t *testing.T) {
-	atom := NewUint32(42)
-
-	require.Equal(t, uint32(42), atom.Load(), "Load didn't work.")
-	require.Equal(t, uint32(46), atom.Add(4), "Add didn't work.")
-	require.Equal(t, uint32(44), atom.Sub(2), "Sub didn't work.")
-	require.Equal(t, uint32(45), atom.Inc(), "Inc didn't work.")
-	require.Equal(t, uint32(44), atom.Dec(), "Dec didn't work.")
-
-	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
-	require.Equal(t, uint32(0), atom.Load(), "CAS didn't set the correct value.")
-
-	require.Equal(t, uint32(0), atom.Swap(1), "Swap didn't return the old value.")
-	require.Equal(t, uint32(1), atom.Load(), "Swap didn't set the correct value.")
-
-	atom.Store(42)
-	require.Equal(t, uint32(42), atom.Load(), "Store didn't set the correct value.")
-
-	t.Run("JSON/Marshal", func(t *testing.T) {
-		bytes, err := json.Marshal(atom)
-		require.NoError(t, err, "json.Marshal errored unexpectedly.")
-		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
-	})
-
-	t.Run("JSON/Unmarshal", func(t *testing.T) {
-		err := json.Unmarshal([]byte("40"), &atom)
-		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
-		require.Equal(t, uint32(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
-	})
-
-	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
-		err := json.Unmarshal([]byte(`"40"`), &atom)
-		require.Error(t, err, "json.Unmarshal didn't error as expected.")
-		assertErrorJSONUnmarshalType(t, err,
-			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
-	})
-}
-
-func TestUint64(t *testing.T) {
-	atom := NewUint64(42)
-
-	require.Equal(t, uint64(42), atom.Load(), "Load didn't work.")
-	require.Equal(t, uint64(46), atom.Add(4), "Add didn't work.")
-	require.Equal(t, uint64(44), atom.Sub(2), "Sub didn't work.")
-	require.Equal(t, uint64(45), atom.Inc(), "Inc didn't work.")
-	require.Equal(t, uint64(44), atom.Dec(), "Dec didn't work.")
-
-	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
-	require.Equal(t, uint64(0), atom.Load(), "CAS didn't set the correct value.")
-
-	require.Equal(t, uint64(0), atom.Swap(1), "Swap didn't return the old value.")
-	require.Equal(t, uint64(1), atom.Load(), "Swap didn't set the correct value.")
-
-	atom.Store(42)
-	require.Equal(t, uint64(42), atom.Load(), "Store didn't set the correct value.")
-
-	t.Run("JSON/Marshal", func(t *testing.T) {
-		bytes, err := json.Marshal(atom)
-		require.NoError(t, err, "json.Marshal errored unexpectedly.")
-		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
-	})
-
-	t.Run("JSON/Unmarshal", func(t *testing.T) {
-		err := json.Unmarshal([]byte("40"), &atom)
-		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
-		require.Equal(t, uint64(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
-	})
-
-	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
-		err := json.Unmarshal([]byte(`"40"`), &atom)
-		require.Error(t, err, "json.Unmarshal didn't error as expected.")
-		assertErrorJSONUnmarshalType(t, err,
-			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
-	})
-}
-
 func TestBool(t *testing.T) {
 	atom := NewBool(false)
 	require.False(t, atom.Toggle(), "Expected Toggle to return previous value.")
@@ -253,7 +101,7 @@
 	})
 
 	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
-		err := json.Unmarshal([]byte(`"40.5"`), &atom)
+		err := json.Unmarshal([]byte("\"40.5\""), &atom)
 		require.Error(t, err, "json.Unmarshal didn't error as expected.")
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
@@ -290,7 +138,7 @@
 	})
 
 	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
-		err := json.Unmarshal([]byte(`"1000000000"`), &atom)
+		err := json.Unmarshal([]byte("\"1000000000\""), &atom)
 		require.Error(t, err, "json.Unmarshal didn't error as expected.")
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
diff --git a/error.go b/error.go
index 0489d19..fb66bd5 100644
--- a/error.go
+++ b/error.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2016 Uber Technologies, Inc.
+// Copyright (c) 2020 Uber Technologies, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -20,36 +20,40 @@
 
 package atomic
 
-// Error is an atomic type-safe wrapper around Value for errors
+// Error is an atomic type-safe wrapper for error values.
 type Error struct{ v Value }
 
-// errorHolder is non-nil holder for error object.
-// atomic.Value panics on saving nil object, so err object needs to be
-// wrapped with valid object first.
-type errorHolder struct{ err error }
+type storedError struct{ Value error }
 
-// NewError creates new atomic error object
-func NewError(err error) *Error {
-	e := &Error{}
-	if err != nil {
-		e.Store(err)
-	}
-	return e
+func wrapError(v error) storedError {
+	return storedError{v}
 }
 
-// Load atomically loads the wrapped error
-func (e *Error) Load() error {
-	v := e.v.Load()
+func unwrapError(v storedError) error {
+	return v.Value
+}
+
+// NewError creates a new Error.
+func NewError(v error) *Error {
+	x := &Error{}
+	if v != nil {
+		x.Store(v)
+	}
+	return x
+}
+
+// Load atomically loads the wrapped error.
+func (x *Error) Load() error {
+	v := x.v.Load()
 	if v == nil {
 		return nil
 	}
-
-	eh := v.(errorHolder)
-	return eh.err
+	return unwrapError(v.(storedError))
 }
 
-// Store atomically stores error.
-// NOTE: a holder object is allocated on each Store call.
-func (e *Error) Store(err error) {
-	e.v.Store(errorHolder{err: err})
+// Store atomically stores the passed error.
+//
+// NOTE: This will cause an allocation.
+func (x *Error) Store(v error) {
+	x.v.Store(wrapError(v))
 }
diff --git a/gen.go b/gen.go
new file mode 100644
index 0000000..03c8ac9
--- /dev/null
+++ b/gen.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+//go:generate bin/gen-atomicint -name=Int32 -wrapped=int32 -file=int32.go
+//go:generate bin/gen-atomicint -name=Int64 -wrapped=int64 -file=int64.go
+//go:generate bin/gen-atomicint -name=Uint32 -wrapped=uint32 -unsigned -file=uint32.go
+//go:generate bin/gen-atomicint -name=Uint64 -wrapped=uint64 -unsigned -file=uint64.go
+
+//go:generate bin/gen-valuewrapper -name=String -type=string -zero="" -file=string.go
+//go:generate bin/gen-valuewrapper -name=Error -type=error -zero=nil -file=error.go
diff --git a/int32.go b/int32.go
new file mode 100644
index 0000000..cdd2844
--- /dev/null
+++ b/int32.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"sync/atomic"
+)
+
+// Int32 is an atomic wrapper around int32.
+type Int32 struct{ v int32 }
+
+// NewInt32 creates a new Int32.
+func NewInt32(i int32) *Int32 {
+	return &Int32{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Int32) Load() int32 {
+	return atomic.LoadInt32(&i.v)
+}
+
+// Add atomically adds to the wrapped int32 and returns the new value.
+func (i *Int32) Add(n int32) int32 {
+	return atomic.AddInt32(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped int32 and returns the new value.
+func (i *Int32) Sub(n int32) int32 {
+	return atomic.AddInt32(&i.v, -n)
+}
+
+// Inc atomically increments the wrapped int32 and returns the new value.
+func (i *Int32) Inc() int32 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped int32 and returns the new value.
+func (i *Int32) Dec() int32 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Int32) CAS(old, new int32) bool {
+	return atomic.CompareAndSwapInt32(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Int32) Store(n int32) {
+	atomic.StoreInt32(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped int32 and returns the old value.
+func (i *Int32) Swap(n int32) int32 {
+	return atomic.SwapInt32(&i.v, n)
+}
+
+// MarshalJSON encodes the wrapped int32 into JSON.
+func (i *Int32) MarshalJSON() ([]byte, error) {
+	return json.Marshal(i.Load())
+}
+
+// UnmarshalJSON decodes JSON into the wrapped int32.
+func (i *Int32) UnmarshalJSON(b []byte) error {
+	var v int32
+	if err := json.Unmarshal(b, &v); err != nil {
+		return err
+	}
+	i.Store(v)
+	return nil
+}
diff --git a/int32_test.go b/int32_test.go
new file mode 100644
index 0000000..19c9ef7
--- /dev/null
+++ b/int32_test.go
@@ -0,0 +1,66 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestInt32(t *testing.T) {
+	atom := NewInt32(42)
+
+	require.Equal(t, int32(42), atom.Load(), "Load didn't work.")
+	require.Equal(t, int32(46), atom.Add(4), "Add didn't work.")
+	require.Equal(t, int32(44), atom.Sub(2), "Sub didn't work.")
+	require.Equal(t, int32(45), atom.Inc(), "Inc didn't work.")
+	require.Equal(t, int32(44), atom.Dec(), "Dec didn't work.")
+
+	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
+	require.Equal(t, int32(0), atom.Load(), "CAS didn't set the correct value.")
+
+	require.Equal(t, int32(0), atom.Swap(1), "Swap didn't return the old value.")
+	require.Equal(t, int32(1), atom.Load(), "Swap didn't set the correct value.")
+
+	atom.Store(42)
+	require.Equal(t, int32(42), atom.Load(), "Store didn't set the correct value.")
+
+	t.Run("JSON/Marshal", func(t *testing.T) {
+		bytes, err := json.Marshal(atom)
+		require.NoError(t, err, "json.Marshal errored unexpectedly.")
+		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
+	})
+
+	t.Run("JSON/Unmarshal", func(t *testing.T) {
+		err := json.Unmarshal([]byte("40"), &atom)
+		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
+		require.Equal(t, int32(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
+	})
+
+	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
+		err := json.Unmarshal([]byte(`"40"`), &atom)
+		require.Error(t, err, "json.Unmarshal didn't error as expected.")
+		assertErrorJSONUnmarshalType(t, err,
+			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
+	})
+}
diff --git a/int64.go b/int64.go
new file mode 100644
index 0000000..41c7fef
--- /dev/null
+++ b/int64.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"sync/atomic"
+)
+
+// Int64 is an atomic wrapper around int64.
+type Int64 struct{ v int64 }
+
+// NewInt64 creates a new Int64.
+func NewInt64(i int64) *Int64 {
+	return &Int64{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Int64) Load() int64 {
+	return atomic.LoadInt64(&i.v)
+}
+
+// Add atomically adds to the wrapped int64 and returns the new value.
+func (i *Int64) Add(n int64) int64 {
+	return atomic.AddInt64(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped int64 and returns the new value.
+func (i *Int64) Sub(n int64) int64 {
+	return atomic.AddInt64(&i.v, -n)
+}
+
+// Inc atomically increments the wrapped int64 and returns the new value.
+func (i *Int64) Inc() int64 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped int64 and returns the new value.
+func (i *Int64) Dec() int64 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Int64) CAS(old, new int64) bool {
+	return atomic.CompareAndSwapInt64(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Int64) Store(n int64) {
+	atomic.StoreInt64(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped int64 and returns the old value.
+func (i *Int64) Swap(n int64) int64 {
+	return atomic.SwapInt64(&i.v, n)
+}
+
+// MarshalJSON encodes the wrapped int64 into JSON.
+func (i *Int64) MarshalJSON() ([]byte, error) {
+	return json.Marshal(i.Load())
+}
+
+// UnmarshalJSON decodes JSON into the wrapped int64.
+func (i *Int64) UnmarshalJSON(b []byte) error {
+	var v int64
+	if err := json.Unmarshal(b, &v); err != nil {
+		return err
+	}
+	i.Store(v)
+	return nil
+}
diff --git a/int64_test.go b/int64_test.go
new file mode 100644
index 0000000..b012196
--- /dev/null
+++ b/int64_test.go
@@ -0,0 +1,66 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestInt64(t *testing.T) {
+	atom := NewInt64(42)
+
+	require.Equal(t, int64(42), atom.Load(), "Load didn't work.")
+	require.Equal(t, int64(46), atom.Add(4), "Add didn't work.")
+	require.Equal(t, int64(44), atom.Sub(2), "Sub didn't work.")
+	require.Equal(t, int64(45), atom.Inc(), "Inc didn't work.")
+	require.Equal(t, int64(44), atom.Dec(), "Dec didn't work.")
+
+	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
+	require.Equal(t, int64(0), atom.Load(), "CAS didn't set the correct value.")
+
+	require.Equal(t, int64(0), atom.Swap(1), "Swap didn't return the old value.")
+	require.Equal(t, int64(1), atom.Load(), "Swap didn't set the correct value.")
+
+	atom.Store(42)
+	require.Equal(t, int64(42), atom.Load(), "Store didn't set the correct value.")
+
+	t.Run("JSON/Marshal", func(t *testing.T) {
+		bytes, err := json.Marshal(atom)
+		require.NoError(t, err, "json.Marshal errored unexpectedly.")
+		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
+	})
+
+	t.Run("JSON/Unmarshal", func(t *testing.T) {
+		err := json.Unmarshal([]byte("40"), &atom)
+		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
+		require.Equal(t, int64(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
+	})
+
+	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
+		err := json.Unmarshal([]byte(`"40"`), &atom)
+		require.Error(t, err, "json.Unmarshal didn't error as expected.")
+		assertErrorJSONUnmarshalType(t, err,
+			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
+	})
+}
diff --git a/internal/gen-atomicint/main.go b/internal/gen-atomicint/main.go
new file mode 100644
index 0000000..39ca6c8
--- /dev/null
+++ b/internal/gen-atomicint/main.go
@@ -0,0 +1,201 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+// gen-atomicint generates an atomic wrapper around an integer type.
+//
+//  gen-atomicint -name Int32 -wrapped int32 -file out.go
+//
+// The generated wrapper will use the functions in the sync/atomic package
+// named after the generated type.
+package main
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"go/format"
+	"io"
+	"log"
+	"os"
+	"text/template"
+)
+
+func main() {
+	log.SetFlags(0)
+	if err := run(os.Args[1:]); err != nil {
+		log.Fatalf("%+v", err)
+	}
+}
+
+func run(args []string) error {
+	var opts struct {
+		Name     string
+		Wrapped  string
+		File     string
+		Unsigned bool
+	}
+
+	flag := flag.NewFlagSet("gen-atomicint", flag.ContinueOnError)
+
+	flag.StringVar(&opts.Name, "name", "", "name of the generated type (e.g. Int32)")
+	flag.StringVar(&opts.Wrapped, "wrapped", "", "name of the wrapped type (e.g. int32)")
+	flag.StringVar(&opts.File, "file", "", "output file path (default: stdout)")
+	flag.BoolVar(&opts.Unsigned, "unsigned", false, "whether the type is unsigned")
+
+	if err := flag.Parse(args); err != nil {
+		return err
+	}
+
+	if len(opts.Name) == 0 || len(opts.Wrapped) == 0 {
+		return errors.New("flags -name and -wrapped are required")
+	}
+
+	var w io.Writer = os.Stdout
+	if file := opts.File; len(file) > 0 {
+		f, err := os.Create(file)
+		if err != nil {
+			return fmt.Errorf("create %q: %v", file, err)
+		}
+		defer f.Close()
+
+		w = f
+	}
+
+	data := struct {
+		Name     string
+		Wrapped  string
+		Unsigned bool
+	}{
+		Name:     opts.Name,
+		Wrapped:  opts.Wrapped,
+		Unsigned: opts.Unsigned,
+	}
+
+	var buff bytes.Buffer
+	if err := _tmpl.Execute(&buff, data); err != nil {
+		return fmt.Errorf("render template: %v", err)
+	}
+
+	bs, err := format.Source(buff.Bytes())
+	if err != nil {
+		return fmt.Errorf("reformat source: %v", err)
+	}
+
+	_, err = w.Write(bs)
+	return err
+}
+
+var _tmpl = template.Must(template.New("int.go").Parse(`// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"sync/atomic"
+)
+
+// {{ .Name }} is an atomic wrapper around {{ .Wrapped }}.
+type {{ .Name }} struct{ v {{ .Wrapped }} }
+
+// New{{ .Name }} creates a new {{ .Name }}.
+func New{{ .Name }}(i {{ .Wrapped }}) *{{ .Name }} {
+	return &{{ .Name }}{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *{{ .Name }}) Load() {{ .Wrapped }} {
+	return atomic.Load{{ .Name }}(&i.v)
+}
+
+// Add atomically adds to the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Add(n {{ .Wrapped }}) {{ .Wrapped }} {
+	return atomic.Add{{ .Name }}(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Sub(n {{ .Wrapped }}) {{ .Wrapped }} {
+	return atomic.Add{{ .Name }}(&i.v,
+		{{- if .Unsigned -}}
+			^(n - 1)
+		{{- else -}}
+			-n
+		{{- end -}}
+	)
+}
+
+// Inc atomically increments the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Inc() {{ .Wrapped }} {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped {{ .Wrapped }} and returns the new value.
+func (i *{{ .Name }}) Dec() {{ .Wrapped }} {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *{{ .Name }}) CAS(old, new {{ .Wrapped }}) bool {
+	return atomic.CompareAndSwap{{ .Name }}(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *{{ .Name }}) Store(n {{ .Wrapped }}) {
+	atomic.Store{{ .Name }}(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped {{ .Wrapped }} and returns the old value.
+func (i *{{ .Name }}) Swap(n {{ .Wrapped }}) {{ .Wrapped }} {
+	return atomic.Swap{{ .Name }}(&i.v, n)
+}
+
+// MarshalJSON encodes the wrapped {{ .Wrapped }} into JSON.
+func (i *{{ .Name }}) MarshalJSON() ([]byte, error) {
+	return json.Marshal(i.Load())
+}
+
+// UnmarshalJSON decodes JSON into the wrapped {{ .Wrapped }}.
+func (i *{{ .Name }}) UnmarshalJSON(b []byte) error {
+	var v {{ .Wrapped }}
+	if err := json.Unmarshal(b, &v); err != nil {
+		return err
+	}
+	i.Store(v)
+	return nil
+}
+`))
diff --git a/internal/gen-valuewrapper/main.go b/internal/gen-valuewrapper/main.go
new file mode 100644
index 0000000..24a8f0f
--- /dev/null
+++ b/internal/gen-valuewrapper/main.go
@@ -0,0 +1,171 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package main
+
+import (
+	"bytes"
+	"errors"
+	"flag"
+	"fmt"
+	"go/format"
+	"io"
+	"log"
+	"os"
+	"text/template"
+)
+
+func main() {
+	log.SetFlags(0)
+	if err := run(os.Args[1:]); err != nil {
+		log.Fatalf("%+v", err)
+	}
+}
+
+func run(args []string) error {
+	var opts struct {
+		Name string
+		Type string
+		Zero string
+		File string
+	}
+
+	flag := flag.NewFlagSet("gen-atomicint", flag.ContinueOnError)
+
+	flag.StringVar(&opts.Name, "name", "", "name of the generated type (e.g. Int32)")
+	flag.StringVar(&opts.Type, "type", "", "name of the wrapped type (e.g. int32)")
+	flag.StringVar(&opts.Zero, "zero", "", "zero value of the wrapped type (e.g. nil)")
+	flag.StringVar(&opts.File, "file", "", "output file path (default: stdout)")
+
+	if err := flag.Parse(args); err != nil {
+		return err
+	}
+
+	if len(opts.Name) == 0 || len(opts.Type) == 0 || len(opts.Zero) == 0 {
+		return errors.New("flags -name, -type, and -zero are required")
+	}
+
+	var w io.Writer = os.Stdout
+	if file := opts.File; len(file) > 0 {
+		f, err := os.Create(file)
+		if err != nil {
+			return fmt.Errorf("create %q: %v", file, err)
+		}
+		defer f.Close()
+
+		w = f
+	}
+
+	data := struct {
+		Name     string
+		Type     string
+		Zero     string
+		Nillable bool
+	}{
+		Name:     opts.Name,
+		Type:     opts.Type,
+		Zero:     opts.Zero,
+		Nillable: opts.Zero == "nil",
+	}
+
+	var buff bytes.Buffer
+	if err := _tmpl.Execute(&buff, data); err != nil {
+		return fmt.Errorf("render template: %v", err)
+	}
+
+	bs, err := format.Source(buff.Bytes())
+	if err != nil {
+		return fmt.Errorf("reformat source: %v", err)
+	}
+
+	_, err = w.Write(bs)
+	return err
+}
+
+var _tmpl = template.Must(template.New("int.go").Parse(`// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+// {{ .Name }} is an atomic type-safe wrapper for {{ .Type }} values.
+type {{ .Name }} struct{ v Value }
+
+{{/* atomic.Value panics for nil values. Generate a wrapper for these types. */}}
+{{ $stored := .Type }}
+{{ $wrap := .Type }}
+{{ $unwrap := .Type }}
+{{ if .Nillable -}}
+	{{ $stored = printf "stored%s" .Name }}
+	{{ $wrap = printf "wrap%s" .Name }}
+	{{ $unwrap = printf "unwrap%s" .Name }}
+
+	type {{ $stored }} struct{ Value {{ .Type }} }
+
+	func {{ $wrap }}(v {{ .Type }}) {{ $stored }} {
+		return {{ $stored }}{v}
+	}
+
+	func {{ $unwrap }}(v {{ $stored }}) {{ .Type }} {
+		return v.Value
+	}
+{{- end }}
+
+// New{{ .Name }} creates a new {{ .Name }}.
+func New{{ .Name }}(v {{ .Type }}) *{{ .Name }} {
+	x := &{{ .Name }}{}
+	if v != {{ .Zero }} {
+		x.Store(v)
+	}
+	return x
+}
+
+// Load atomically loads the wrapped {{ .Type }}.
+func (x *{{ .Name }}) Load() {{ .Type }} {
+	v := x.v.Load()
+	if v == nil {
+		return {{ .Zero }}
+	}
+	return {{ $unwrap }}(v.({{ $stored }}))
+}
+
+// Store atomically stores the passed {{ .Type }}.
+//
+// NOTE: This will cause an allocation.
+func (x *{{ .Name }}) Store(v {{ .Type }}) {
+	x.v.Store({{ $wrap }}(v))
+}
+`))
diff --git a/string.go b/string.go
index fc95659..1b8c49c 100644
--- a/string.go
+++ b/string.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2016-2020 Uber Technologies, Inc.
+// Copyright (c) 2020 Uber Technologies, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -20,45 +20,30 @@
 
 package atomic
 
-// String is an atomic type-safe wrapper around Value for strings.
+// String is an atomic type-safe wrapper for string values.
 type String struct{ v Value }
 
-// NewString creates a String.
-func NewString(str string) *String {
-	s := &String{}
-	if str != "" {
-		s.Store(str)
+// NewString creates a new String.
+func NewString(v string) *String {
+	x := &String{}
+	if v != "" {
+		x.Store(v)
 	}
-	return s
+	return x
 }
 
 // Load atomically loads the wrapped string.
-func (s *String) Load() string {
-	v := s.v.Load()
+func (x *String) Load() string {
+	v := x.v.Load()
 	if v == nil {
 		return ""
 	}
-	return v.(string)
-}
-
-// MarshalText encodes the wrapped string into a textual form.
-//
-// This makes it encodable as JSON, YAML, XML, and more.
-func (s *String) MarshalText() ([]byte, error) {
-	return []byte(s.Load()), nil
-}
-
-// UnmarshalText decodes text and replaces the wrapped string with it.
-//
-// This makes it decodable from JSON, YAML, XML, and more.
-func (s *String) UnmarshalText(b []byte) error {
-	s.Store(string(b))
-	return nil
+	return string(v.(string))
 }
 
 // Store atomically stores the passed string.
-// Note: Converting the string to an interface{} to store in the Value
-// requires an allocation.
-func (s *String) Store(str string) {
-	s.v.Store(str)
+//
+// NOTE: This will cause an allocation.
+func (x *String) Store(v string) {
+	x.v.Store(string(v))
 }
diff --git a/string_ext.go b/string_ext.go
new file mode 100644
index 0000000..c0357e3
--- /dev/null
+++ b/string_ext.go
@@ -0,0 +1,36 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+// MarshalText encodes the wrapped string into a textual form.
+//
+// This makes it encodable as JSON, YAML, XML, and more.
+func (s *String) MarshalText() ([]byte, error) {
+	return []byte(s.Load()), nil
+}
+
+// UnmarshalText decodes text and replaces the wrapped string with it.
+//
+// This makes it decodable from JSON, YAML, XML, and more.
+func (s *String) UnmarshalText(b []byte) error {
+	s.Store(string(b))
+	return nil
+}
diff --git a/uint32.go b/uint32.go
new file mode 100644
index 0000000..aa5d5b1
--- /dev/null
+++ b/uint32.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"sync/atomic"
+)
+
+// Uint32 is an atomic wrapper around uint32.
+type Uint32 struct{ v uint32 }
+
+// NewUint32 creates a new Uint32.
+func NewUint32(i uint32) *Uint32 {
+	return &Uint32{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Uint32) Load() uint32 {
+	return atomic.LoadUint32(&i.v)
+}
+
+// Add atomically adds to the wrapped uint32 and returns the new value.
+func (i *Uint32) Add(n uint32) uint32 {
+	return atomic.AddUint32(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped uint32 and returns the new value.
+func (i *Uint32) Sub(n uint32) uint32 {
+	return atomic.AddUint32(&i.v, ^(n - 1))
+}
+
+// Inc atomically increments the wrapped uint32 and returns the new value.
+func (i *Uint32) Inc() uint32 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped uint32 and returns the new value.
+func (i *Uint32) Dec() uint32 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Uint32) CAS(old, new uint32) bool {
+	return atomic.CompareAndSwapUint32(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Uint32) Store(n uint32) {
+	atomic.StoreUint32(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped uint32 and returns the old value.
+func (i *Uint32) Swap(n uint32) uint32 {
+	return atomic.SwapUint32(&i.v, n)
+}
+
+// MarshalJSON encodes the wrapped uint32 into JSON.
+func (i *Uint32) MarshalJSON() ([]byte, error) {
+	return json.Marshal(i.Load())
+}
+
+// UnmarshalJSON decodes JSON into the wrapped uint32.
+func (i *Uint32) UnmarshalJSON(b []byte) error {
+	var v uint32
+	if err := json.Unmarshal(b, &v); err != nil {
+		return err
+	}
+	i.Store(v)
+	return nil
+}
diff --git a/uint32_test.go b/uint32_test.go
new file mode 100644
index 0000000..4149a47
--- /dev/null
+++ b/uint32_test.go
@@ -0,0 +1,66 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestUint32(t *testing.T) {
+	atom := NewUint32(42)
+
+	require.Equal(t, uint32(42), atom.Load(), "Load didn't work.")
+	require.Equal(t, uint32(46), atom.Add(4), "Add didn't work.")
+	require.Equal(t, uint32(44), atom.Sub(2), "Sub didn't work.")
+	require.Equal(t, uint32(45), atom.Inc(), "Inc didn't work.")
+	require.Equal(t, uint32(44), atom.Dec(), "Dec didn't work.")
+
+	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
+	require.Equal(t, uint32(0), atom.Load(), "CAS didn't set the correct value.")
+
+	require.Equal(t, uint32(0), atom.Swap(1), "Swap didn't return the old value.")
+	require.Equal(t, uint32(1), atom.Load(), "Swap didn't set the correct value.")
+
+	atom.Store(42)
+	require.Equal(t, uint32(42), atom.Load(), "Store didn't set the correct value.")
+
+	t.Run("JSON/Marshal", func(t *testing.T) {
+		bytes, err := json.Marshal(atom)
+		require.NoError(t, err, "json.Marshal errored unexpectedly.")
+		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
+	})
+
+	t.Run("JSON/Unmarshal", func(t *testing.T) {
+		err := json.Unmarshal([]byte("40"), &atom)
+		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
+		require.Equal(t, uint32(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
+	})
+
+	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
+		err := json.Unmarshal([]byte(`"40"`), &atom)
+		require.Error(t, err, "json.Unmarshal didn't error as expected.")
+		assertErrorJSONUnmarshalType(t, err,
+			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
+	})
+}
diff --git a/uint64.go b/uint64.go
new file mode 100644
index 0000000..3771d96
--- /dev/null
+++ b/uint64.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"sync/atomic"
+)
+
+// Uint64 is an atomic wrapper around uint64.
+type Uint64 struct{ v uint64 }
+
+// NewUint64 creates a new Uint64.
+func NewUint64(i uint64) *Uint64 {
+	return &Uint64{i}
+}
+
+// Load atomically loads the wrapped value.
+func (i *Uint64) Load() uint64 {
+	return atomic.LoadUint64(&i.v)
+}
+
+// Add atomically adds to the wrapped uint64 and returns the new value.
+func (i *Uint64) Add(n uint64) uint64 {
+	return atomic.AddUint64(&i.v, n)
+}
+
+// Sub atomically subtracts from the wrapped uint64 and returns the new value.
+func (i *Uint64) Sub(n uint64) uint64 {
+	return atomic.AddUint64(&i.v, ^(n - 1))
+}
+
+// Inc atomically increments the wrapped uint64 and returns the new value.
+func (i *Uint64) Inc() uint64 {
+	return i.Add(1)
+}
+
+// Dec atomically decrements the wrapped uint64 and returns the new value.
+func (i *Uint64) Dec() uint64 {
+	return i.Sub(1)
+}
+
+// CAS is an atomic compare-and-swap.
+func (i *Uint64) CAS(old, new uint64) bool {
+	return atomic.CompareAndSwapUint64(&i.v, old, new)
+}
+
+// Store atomically stores the passed value.
+func (i *Uint64) Store(n uint64) {
+	atomic.StoreUint64(&i.v, n)
+}
+
+// Swap atomically swaps the wrapped uint64 and returns the old value.
+func (i *Uint64) Swap(n uint64) uint64 {
+	return atomic.SwapUint64(&i.v, n)
+}
+
+// MarshalJSON encodes the wrapped uint64 into JSON.
+func (i *Uint64) MarshalJSON() ([]byte, error) {
+	return json.Marshal(i.Load())
+}
+
+// UnmarshalJSON decodes JSON into the wrapped uint64.
+func (i *Uint64) UnmarshalJSON(b []byte) error {
+	var v uint64
+	if err := json.Unmarshal(b, &v); err != nil {
+		return err
+	}
+	i.Store(v)
+	return nil
+}
diff --git a/uint64_test.go b/uint64_test.go
new file mode 100644
index 0000000..7ff370b
--- /dev/null
+++ b/uint64_test.go
@@ -0,0 +1,66 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package atomic
+
+import (
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestUint64(t *testing.T) {
+	atom := NewUint64(42)
+
+	require.Equal(t, uint64(42), atom.Load(), "Load didn't work.")
+	require.Equal(t, uint64(46), atom.Add(4), "Add didn't work.")
+	require.Equal(t, uint64(44), atom.Sub(2), "Sub didn't work.")
+	require.Equal(t, uint64(45), atom.Inc(), "Inc didn't work.")
+	require.Equal(t, uint64(44), atom.Dec(), "Dec didn't work.")
+
+	require.True(t, atom.CAS(44, 0), "CAS didn't report a swap.")
+	require.Equal(t, uint64(0), atom.Load(), "CAS didn't set the correct value.")
+
+	require.Equal(t, uint64(0), atom.Swap(1), "Swap didn't return the old value.")
+	require.Equal(t, uint64(1), atom.Load(), "Swap didn't set the correct value.")
+
+	atom.Store(42)
+	require.Equal(t, uint64(42), atom.Load(), "Store didn't set the correct value.")
+
+	t.Run("JSON/Marshal", func(t *testing.T) {
+		bytes, err := json.Marshal(atom)
+		require.NoError(t, err, "json.Marshal errored unexpectedly.")
+		require.Equal(t, []byte("42"), bytes, "json.Marshal encoded the wrong bytes.")
+	})
+
+	t.Run("JSON/Unmarshal", func(t *testing.T) {
+		err := json.Unmarshal([]byte("40"), &atom)
+		require.NoError(t, err, "json.Unmarshal errored unexpectedly.")
+		require.Equal(t, uint64(40), atom.Load(), "json.Unmarshal didn't set the correct value.")
+	})
+
+	t.Run("JSON/Unmarshal/Error", func(t *testing.T) {
+		err := json.Unmarshal([]byte(`"40"`), &atom)
+		require.Error(t, err, "json.Unmarshal didn't error as expected.")
+		assertErrorJSONUnmarshalType(t, err,
+			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
+	})
+}