From c095b9c8ee48116c45ab25b851845d302a7fc5a4 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Mon, 13 Feb 2023 18:46:37 -0500 Subject: [PATCH] Allow noinit to apply to slices, maps, and unsafe pointers (#83) Previously, this would throw an error. Fixes GH-82. --- README.md | 8 ++++---- envconfig.go | 6 +++++- envconfig_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3943cb1..51f0f8e 100644 --- a/README.md +++ b/README.md @@ -305,8 +305,8 @@ export APP_MYVAR="foo" ## Initialization -By default, all pointer fields are initialized (allocated) so they are not -`nil`. To disable this behavior, use the tag the field as `noinit`: +By default, all pointers, slices, and maps are initialized (allocated) so they +are not `nil`. To disable this behavior, use the tag the field as `noinit`: ```go type MyStruct struct { @@ -332,8 +332,8 @@ type ChildConfig struct { } ``` -The `noinit` tag is only applicable for pointer fields. Putting the tag on a -non-struct-pointer will return an error. +The `noinit` tag is only applicable for pointer, slice, and map fields. Putting +the tag on a different type will return an error. ## Extension diff --git a/envconfig.go b/envconfig.go index a28fd11..2d52059 100644 --- a/envconfig.go +++ b/envconfig.go @@ -301,7 +301,11 @@ func processWith(ctx context.Context, i interface{}, l Lookuper, parentNoInit bo } // NoInit is only permitted on pointers. - if opts.NoInit && ef.Kind() != reflect.Ptr { + if opts.NoInit && + ef.Kind() != reflect.Ptr && + ef.Kind() != reflect.Slice && + ef.Kind() != reflect.Map && + ef.Kind() != reflect.UnsafePointer { return fmt.Errorf("%s: %w", tf.Name, ErrNoInitNotPtr) } shouldNotInit := opts.NoInit || parentNoInit diff --git a/envconfig_test.go b/envconfig_test.go index d62508a..ae2cf04 100644 --- a/envconfig_test.go +++ b/envconfig_test.go @@ -26,6 +26,7 @@ import ( "strings" "testing" "time" + "unsafe" "github.com/google/go-cmp/cmp" ) @@ -2227,6 +2228,42 @@ func TestProcessWith(t *testing.T) { "FIELD2": "5", }), }, + { + name: "noinit/map", + input: &struct { + Field map[string]string `env:"FIELD, noinit"` + }{}, + exp: &struct { + Field map[string]string `env:"FIELD, noinit"` + }{ + Field: nil, + }, + lookuper: MapLookuper(nil), + }, + { + name: "noinit/slice", + input: &struct { + Field []string `env:"FIELD, noinit"` + }{}, + exp: &struct { + Field []string `env:"FIELD, noinit"` + }{ + Field: nil, + }, + lookuper: MapLookuper(nil), + }, + { + name: "noinit/unsafe_pointer", + input: &struct { + Field unsafe.Pointer `env:"FIELD, noinit"` + }{}, + exp: &struct { + Field unsafe.Pointer `env:"FIELD, noinit"` + }{ + Field: nil, + }, + lookuper: MapLookuper(nil), + }, { name: "noinit/error_not_ptr", input: &struct {