Repository: uber-go/atomic (go)

Issue:
Add String() representations?
If I have an atomic.String and I try to use fmt.Sprintf("value: %v", atomicStr) I would expect "value: foo" but instead I get "value: {{{foo}}}"


Discussion:

Patch:
diff --git a/atomic.go b/atomic.go
--- a/atomic.go
+++ b/atomic.go
@@ -25,6 +25,7 @@ package atomic
 import (
 	"encoding/json"
 	"math"
+	"strconv"
 	"sync/atomic"
 	"time"
 )
@@ -97,6 +98,11 @@ func (b *Bool) UnmarshalJSON(t []byte) error {
 	return nil
 }
 
+// String encodes the wrapped value as a string.
+func (b *Bool) String() string {
+	return strconv.FormatBool(b.Load())
+}
+
 // Float64 is an atomic wrapper around float64.
 type Float64 struct {
 	nocmp // disallow non-atomic comparison
@@ -155,6 +161,12 @@ func (f *Float64) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
+// String encodes the wrapped value as a string.
+func (f *Float64) String() string {
+	// 'g' is the behavior for floats with %v.
+	return strconv.FormatFloat(f.Load(), 'g', -1, 64)
+}
+
 // Duration is an atomic wrapper around time.Duration
 // https://godoc.org/time#Duration
 type Duration struct {
@@ -213,6 +225,11 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
 	return nil
 }
 
+// String encodes the wrapped value as a string.
+func (d *Duration) String() string {
+	return d.Load().String()
+}
+
 // Value shadows the type of the same name from sync/atomic
 // https://godoc.org/sync/atomic#Value
 type Value struct {
diff --git a/int32.go b/int32.go
--- a/int32.go
+++ b/int32.go
@@ -24,6 +24,7 @@ package atomic
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@ func (i *Int32) UnmarshalJSON(b []byte) error {
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Int32) String() string {
+	v := i.Load()
+	return strconv.FormatInt(int64(v), 10)
+}
diff --git a/int64.go b/int64.go
--- a/int64.go
+++ b/int64.go
@@ -24,6 +24,7 @@ package atomic
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@ func (i *Int64) UnmarshalJSON(b []byte) error {
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Int64) String() string {
+	v := i.Load()
+	return strconv.FormatInt(int64(v), 10)
+}
diff --git a/internal/gen-atomicint/main.go b/internal/gen-atomicint/main.go
--- a/internal/gen-atomicint/main.go
+++ b/internal/gen-atomicint/main.go
@@ -129,6 +129,7 @@ package atomic
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -204,4 +205,14 @@ func (i *{{ .Name }}) UnmarshalJSON(b []byte) error {
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *{{ .Name }}) String() string {
+	v := i.Load()
+	{{ if .Unsigned -}}
+		return strconv.FormatUint(uint64(v), 10)
+	{{- else -}}
+		return strconv.FormatInt(int64(v), 10)
+	{{- end }}
+}
 `))
diff --git a/string_ext.go b/string_ext.go
--- a/string_ext.go
+++ b/string_ext.go
@@ -20,6 +20,11 @@
 
 package atomic
 
+// String returns the wrapped value.
+func (s *String) String() string {
+	return s.Load()
+}
+
 // MarshalText encodes the wrapped string into a textual form.
 //
 // This makes it encodable as JSON, YAML, XML, and more.
diff --git a/uint32.go b/uint32.go
--- a/uint32.go
+++ b/uint32.go
@@ -24,6 +24,7 @@ package atomic
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@ func (i *Uint32) UnmarshalJSON(b []byte) error {
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Uint32) String() string {
+	v := i.Load()
+	return strconv.FormatUint(uint64(v), 10)
+}
diff --git a/uint64.go b/uint64.go
--- a/uint64.go
+++ b/uint64.go
@@ -24,6 +24,7 @@ package atomic
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@ func (i *Uint64) UnmarshalJSON(b []byte) error {
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Uint64) String() string {
+	v := i.Load()
+	return strconv.FormatUint(uint64(v), 10)
+}


Test patch:
diff --git a/atomic_test.go b/atomic_test.go
--- a/atomic_test.go
+++ b/atomic_test.go
@@ -71,6 +71,19 @@ func TestBool(t *testing.T) {
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		t.Run("true", func(t *testing.T) {
+			assert.Equal(t, "true", NewBool(true).String(),
+				"String() returned an unexpected value.")
+		})
+
+		t.Run("false", func(t *testing.T) {
+			var b Bool
+			assert.Equal(t, "false", b.String(),
+				"String() returned an unexpected value.")
+		})
+	})
 }
 
 func TestFloat64(t *testing.T) {
@@ -106,6 +119,11 @@ func TestFloat64(t *testing.T) {
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		assert.Equal(t, "42.5", NewFloat64(42.5).String(),
+			"String() returned an unexpected value.")
+	})
 }
 
 func TestDuration(t *testing.T) {
@@ -143,6 +161,11 @@ func TestDuration(t *testing.T) {
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		assert.Equal(t, "42s", NewDuration(42*time.Second).String(),
+			"String() returned an unexpected value.")
+	})
 }
 
 func TestValue(t *testing.T) {
diff --git a/int32_test.go b/int32_test.go
--- a/int32_test.go
+++ b/int32_test.go
@@ -22,8 +22,10 @@ package atomic
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,18 @@ func TestInt32(t *testing.T) {
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		t.Run("positive", func(t *testing.T) {
+			atom := NewInt32(math.MaxInt32)
+			assert.Equal(t, "2147483647", atom.String(),
+				"String() returned an unexpected value.")
+		})
+
+		t.Run("negative", func(t *testing.T) {
+			atom := NewInt32(math.MinInt32)
+			assert.Equal(t, "-2147483648", atom.String(),
+				"String() returned an unexpected value.")
+		})
+	})
 }
diff --git a/int64_test.go b/int64_test.go
--- a/int64_test.go
+++ b/int64_test.go
@@ -22,8 +22,10 @@ package atomic
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,18 @@ func TestInt64(t *testing.T) {
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		t.Run("positive", func(t *testing.T) {
+			atom := NewInt64(math.MaxInt64)
+			assert.Equal(t, "9223372036854775807", atom.String(),
+				"String() returned an unexpected value.")
+		})
+
+		t.Run("negative", func(t *testing.T) {
+			atom := NewInt64(math.MinInt64)
+			assert.Equal(t, "-9223372036854775808", atom.String(),
+				"String() returned an unexpected value.")
+		})
+	})
 }
diff --git a/string_test.go b/string_test.go
--- a/string_test.go
+++ b/string_test.go
@@ -25,6 +25,7 @@ import (
 	"encoding/xml"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -75,4 +76,10 @@ func TestString(t *testing.T) {
 		require.NoError(t, err, "xml.Unmarshal errored unexpectedly.")
 		require.Equal(t, "bar", atom.Load(), "xml.Unmarshal didn't set the correct value.")
 	})
+
+	t.Run("String", func(t *testing.T) {
+		atom := NewString("foo")
+		assert.Equal(t, "foo", atom.String(),
+			"String() returned an unexpected value.")
+	})
 }
diff --git a/uint32_test.go b/uint32_test.go
--- a/uint32_test.go
+++ b/uint32_test.go
@@ -22,8 +22,10 @@ package atomic
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,12 @@ func TestUint32(t *testing.T) {
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		// Use an integer with the signed bit set. If we're converting
+		// incorrectly, we'll get a negative value here.
+		atom := NewUint32(math.MaxUint32)
+		assert.Equal(t, "4294967295", atom.String(),
+			"String() returned an unexpected value.")
+	})
 }
diff --git a/uint64_test.go b/uint64_test.go
--- a/uint64_test.go
+++ b/uint64_test.go
@@ -22,8 +22,10 @@ package atomic
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,12 @@ func TestUint64(t *testing.T) {
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		// Use an integer with the signed bit set. If we're converting
+		// incorrectly, we'll get a negative value here.
+		atom := NewUint64(math.MaxUint64)
+		assert.Equal(t, "18446744073709551615", atom.String(),
+			"String() returned an unexpected value.")
+	})
 }