Avoid extending a constructor with new positional parameters when existing callers already depend on its signature. Adding timeout or context arguments to NewServer breaks compatibility; functional options add configuration without changing the mandatory address argument. section 2.11
Keep configuration in an unexported options struct and define Option as a function that receives a pointer to that struct and returns an error. Each With-prefixed function returns a closure that validates its captured value and updates the configuration. section 2.11.3
Applying options while constructing a server
type Option func(options *options) error func NewServer(addr string, opts ...Option) (*http.Server, error) { var options options for _, opt := range opts { if err := opt(&options); err != nil { return nil, err } } // Apply the completed configuration. } server, err := httplib.NewServer("localhost", httplib.WithPort(8080), httplib.WithTimeout(time.Second)) server, err := httplib.NewServer("localhost") The first call supplies multiple options; the second receives the default configuration without an empty options value. section 2.11.3
Apply each option where its behavior belongs:
- Validate inputs inside the option; WithPort rejects negative values and stores valid values in the configuration. section 2.11.3
- Process the completed configuration afterward; an unset port uses the default, zero selects a random port, and another value is used directly. section 2.11 section 2.11.3
type options struct { #1
port *int
}
type Option func(options *options) error #2
func WithPort(port int) Option { #3
return func(options *options) error {
if port < 0 {
return errors.New("port should be positive")
}
options.port = &port
return nil
}
}
Inspect the compact Option and WithPort definitions to see how closures capture values, validate them, and mutate private configuration. section 2.11.3
