In go, it’s really useful pattern to pass options to functions. It’s a common usecase to have optional arguments to a function. I use it mostly to initialize something with default values and optionally override them.

Primary idea is to use variadic arguments to pass options to a function.

eg:

func NewService(options ...ServiceOption) *Service {
    ...
}

options ...ServiceOption is the variadic argument.

There are two ways to do this.

  1. Using a struct with all the options as a field.
  2. Using functions with each option value as an argument.

Using a struct to pass options

Example usage

package main

type ServiceConfig struct {
    Color string
    Size int
    Type string
}

func main() {
    // Initialize with default values
    s := NewService()


    // Override some of the default values
    s = NewService(&ServiceConfig{
        Color: "red",
        Size: 10,
    })
}

How to setup this options pattern

You can use config... to accept any number of args in a function. Let’s create a config section with init config function.

// file: config.go
package main

type ServiceConfig struct {
    Color string
    Size int
    Type string
}

// initConfig initializes the config with default values
// or overrides them with the passed config
func initConfig(config ...*ServiceConfig) *ServiceConfig {
    var configVals *ServiceConfig
    if len(config) > 0 {
        configVals = config[0]
    } else {
        configVals = &ServiceConfig{}
    }

    if configVals.Color == "" {
        configVals.Color = "blue"
    }

    if configVals.Size == 0 {
        configVals.Size = 5
    }

    if configVals.Type == "" {
        configVals.Type = "default"
    }

    return configVals
}

You can use this function to initialize the config with default values or override them with the passed config. as follows.

// file: service.go
package main

// Service
type Service struct {
    config *ServiceConfig
}

// NewService initializes a new service with default values
func NewService(config ...*ServiceConfig) *Service {
    serviceConfig := initConfig(config...)
    return &Service{
        config: serviceConfig,
    }
}

func (s *Service) GetOutput() string {
    return fmt.Sprintf("Color: %s, Size: %d, Type: %s", s.config.Color, s.config.Size, s.config.Type)
}

and finally, you can use the service as follows.

// file: main.go
package main

import "fmt"

func main() {
    // Initialize with default values
    s1 := NewService()

    fmt.Println(s1.GetOutput())
    // Output:
    // Color: blue, Size: 5, Type: default

    // Override some of the default values
    s2 = NewService(&ServiceConfig{
        Color: "red",
        Size: 10,
    })

    fmt.Println(s2.GetOutput())
    // Output:
    // Color: red, Size: 10, Type: default
}

Using functions to pass options

Example usage

package main

func main() {
    // Initialize with default values
    s := NewService()

    // Override some of the default values
    s = NewService(
        WithColor("red"),
        WithSize(10),
    )
}

How to setup this options pattern

// file: options.go
package main

type ServiceConfig struct {
    Color string
    Size int
    Type string
}

type ServiceOption func(*ServiceConfig)

// functions to override the config values
func WithColor(color string) ServiceOption {
    return func(config *ServiceConfig) {
        config.Color = color
    }
}

func WithSize(size int) ServiceOption {
    return func(config *ServiceConfig) {
        config.Size = size
    }
}

func WithType(t string) ServiceOption {
    return func(config *ServiceConfig) {
        config.Type = t
    }
}

You can use this function to initialize the config with default values or override them with the passed config. as follows.

// file: service.go
package main

// Service
type Service struct {
    config *ServiceConfig
}

// NewService initializes a new service with default values
func NewService(options ...ServiceOption) *Service {
    // default values
    serviceConfig := &ServiceConfig{
        Color: "blue",
        Size: 5,
        Type: "default",
    }

    // override with passed options
    for _, option := range options {
        option(serviceConfig)
    }
    return &Service{
        config: serviceConfig,
    }
}

func (s *Service) GetOutput() string {
    return fmt.Sprintf("Color: %s, Size: %d, Type: %s", s.config.Color, s.config.Size, s.config.Type)
}

and finally, you can use the service as follows.

// file: main.go
package main

import "fmt"

func main() {
    // Initialize with default values
    s1 := NewService()

    fmt.Println(s1.GetOutput())
    // Output:
    // Color: , Size: 0, Type:

    // Override some of the default values
    s2 = NewService(
        WithColor("red"),
        WithSize(10),
    )

    fmt.Println(s2.GetOutput())
    // Output:
    // Color: red, Size: 10, Type:
}

There you go, two ways to use options pattern in go. Enjoy.