Summary
Creating a Viper instance via struct literal (&viper.Viper{}) instead of viper.New() causes a nil map panic on the first call to SetDefault() or Set(). The internal maps (defaults, override, etc.) are not initialized, leading to assignment to entry in nil map.
While viper.New() is the documented constructor, the Viper struct type is exported, so users can construct zero-value instances. A nil-map guard or lazy initialization would make the API more robust.
Reproduction
package main
import "github.com/spf13/viper"
func main() {
v := &viper.Viper{} // zero-value, not viper.New()
v.SetDefault("key", "value")
// panic: assignment to entry in nil map
}
Both SetDefault and Set panic the same way:
goroutine 1 [running]:
github.com/spf13/viper.(*Viper).SetDefault(...)
viper.go:XXX
viper.New() works correctly — it initializes all internal maps.
Why this matters
- Exported type contract: Since
Viper is an exported struct, users may reasonably construct it directly. Go convention (e.g., http.Client{}, sync.Mutex{}) is that zero-value exported types should be usable or at minimum not panic.
- Debugging difficulty: The
assignment to entry in nil map panic gives no hint that the user should have used New() instead of &Viper{}.
- Blast radius: Viper is used by 30k+ projects including Cobra, Hugo, and many CLI tools. A confusing panic during configuration setup wastes developer time.
Suggested Fix
Add nil-map guards at the top of SetDefault and Set:
func (v *Viper) SetDefault(key string, value interface{}) {
if v.defaults == nil {
v.defaults = make(map[string]interface{})
}
// ... existing logic
}
Or alternatively, add a lazyInit() method that ensures all maps are initialized, called at the top of each mutating method.
Environment
- viper v1.19.0
- Go 1.25.1 darwin/arm64
- Panic confirmed on both
SetDefault and Set
Summary
Creating a
Viperinstance via struct literal (&viper.Viper{}) instead ofviper.New()causes a nil map panic on the first call toSetDefault()orSet(). The internal maps (defaults,override, etc.) are not initialized, leading toassignment to entry in nil map.While
viper.New()is the documented constructor, theViperstruct type is exported, so users can construct zero-value instances. A nil-map guard or lazy initialization would make the API more robust.Reproduction
Both
SetDefaultandSetpanic the same way:viper.New()works correctly — it initializes all internal maps.Why this matters
Viperis an exported struct, users may reasonably construct it directly. Go convention (e.g.,http.Client{},sync.Mutex{}) is that zero-value exported types should be usable or at minimum not panic.assignment to entry in nil mappanic gives no hint that the user should have usedNew()instead of&Viper{}.Suggested Fix
Add nil-map guards at the top of
SetDefaultandSet:Or alternatively, add a
lazyInit()method that ensures all maps are initialized, called at the top of each mutating method.Environment
SetDefaultandSet