Go: How to Set Default Values in Structs with Maximum Performance
\n\nHello, Go developer! If you've ever wondered: "Why can't I just write default: inside a struct in Go, like in Python or C++?", you've come to the right place. This topic is one of the hottest questions on Stack Overflow (tens of thousands of views!), and today we'll break it down once and for all.
Go has no built-in mechanism for default values in Go structs. The language creators deliberately chose this to avoid the magic and complexity inherent in other languages. Instead, they offer elegant and performant patterns. We'll cover them all: from simple constructors to advanced techniques with options. And, of course, we'll measure performance so you can choose code that's not only clean but also fast.
\n\nWhy Doesn't Go Support Default Values "Out of the Box"?
\n\nBefore diving into the code, let's understand the philosophy. In Go, every variable has a zero value. For int it's 0, for string it's "", for pointers and slices it's nil. This is done for predictability and safety. If Go allowed overriding these values in the struct declaration, it would lead to confusion: which value is the "real" one — the one set in the struct, or the one we passed during initialization?
Therefore, the Go community has developed several idiomatic ways to solve the problem of setting default values in Go structs. Let's break them down from simple to complex.
\n\nMethod 1: Constructor Function (New()) — The Gold Standard
\n\nThis is the most popular and recommended approach. Instead of creating a struct directly, we write a function that returns an instance with pre-set fields.
\n\npackage config\n\ntype AppConfig struct {\n Port int\n Timeout time.Duration\n Debug bool\n DBHost string\n}\n\n// NewAppConfig — constructor with default values\nfunc NewAppConfig() AppConfig {\n return AppConfig{\n Port: 8080,\n Timeout: 30 * time.Second,\n Debug: false,\n DBHost: "localhost",\n }\n}\n\nUsage:
\ncfg := config.NewAppConfig()\nfmt.Println(cfg.Port) // 8080\n\nWhy is this great? It's explicit, readable, and performant. The Go compiler easily optimizes such code. No reflection, no extra allocations — just returning a struct by value.
\n\nBenchmark (simplified):
\nfunc BenchmarkNewAppConfig(b *testing.B) {\n for i := 0; i < b.N; i++ {\n _ = NewAppConfig()\n }\n}\n// Result: ~0.3 ns/op — practically free\n\nMethod 2: Functional Options Pattern
\n\nThis pattern has become a hallmark of Go. It's ideal when a struct has many fields, most of which are optional. Instead of passing 10 arguments to a constructor, we use option functions.
\n\ntype Server struct {\n host string\n port int\n timeout time.Duration\n maxConn int\n}\n\ntype Option func(*Server)\n\nfunc WithHost(host string) Option {\n return func(s *Server) {\n s.host = host\n }\n}\n\nfunc WithPort(port int) Option {\n return func(s *Server) {\n s.port = port\n }\n}\n\nfunc WithTimeout(t time.Duration) Option {\n return func(s *Server) {\n s.timeout = t\n }\n}\n\n// NewServer — constructor with defaults\nfunc NewServer(opts ...Option) *Server {\n // Set default values in Go structs\n s := &Server{\n host: "0.0.0.0",\n port: 8080,\n timeout: 30 * time.Second,\n maxConn: 100,\n }\n // Apply the passed options\n for _, opt := range opts {\n opt(s)\n }\n return s\n}\n\n