Generate atomic Value wrappers automatically
Rather than hand-writing wrappers around atomic.Value, generate them
automatically from the same template. The generator is at
internal/gen-valuewrapper. The generator correctly handles generating
wrapper structs for nillable types.
diff --git a/.codecov.yml b/.codecov.yml
index 047c0cc..912be70 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -15,3 +15,4 @@
ignore:
- /internal/gen-atomicint/
+ - /internal/gen-valuewrapper/
diff --git a/Makefile b/Makefile
index 5e0ec1c..8d3127f 100644
--- a/Makefile
+++ b/Makefile
@@ -3,6 +3,7 @@
GOLINT = $(GOBIN)/golint
GEN_ATOMICINT = $(GOBIN)/gen-atomicint
+GEN_VALUEWRAPPER = $(GOBIN)/gen-valuewrapper
GO_FILES ?= $(shell find . '(' -path .git -o -path vendor ')' -prune -o -name '*.go' -print)
@@ -23,6 +24,9 @@
$(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
@@ -39,7 +43,7 @@
go tool cover -html=cover.out -o cover.html
.PHONY: generate
-generate: $(GEN_ATOMICINT)
+generate: $(GEN_ATOMICINT) $(GEN_VALUEWRAPPER)
go generate ./...
.PHONY: generatenodirty
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
index 50d6b24..03c8ac9 100644
--- a/gen.go
+++ b/gen.go
@@ -24,3 +24,6 @@
//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/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
+}