Practical Go: Functional Options Pattern

2 min read Tweet this post

Go by default not support set default and optional argument in a function. The simple approach is using variadic argument but only support 1 data type for example func createSomething(input1, input2variadic...string)

To supporting multiple type argument in a function you can use functional options pattern, basically variadic argument with type function. The goals is like code example below

package main

import (
  "log"

  "github.com/xx/yy/api"
)

func main() {
  srv := api.NewServer(
    server.WithHost("lumochift.org"),
    server.WithPort(3000),
  )
  if err := srv.Start(); err != nil {
    log.Fatal(err)
  }
}
package api

type Server struct{
  host string
  port int
}

func NewServer(options ...func(*Server)) *Server {
  srv := &Server{
    host: "localhost",
    port: 8080,
  }

  for _, o := range options {
    o(srv)
  }
  return srv
}

func (s *Server) Start() error {
  // todo
}

func WithHost(host string) func(*Server) {
  return func(s *Server) {
    s.host = host
  }
}

func WithPort(port int) func(*Server) {
  return func(s *Server) {
    s.port = port
  }
}

Using functional options pattern you can select the necessary options only, even without argument (server.New()) the struct creation is valid.

The main problem, if the struct is big we must implement all optional function. Fortunately I already build simple tools for generate optional function.

Install on your system using command go install github.com/lumochift/optgen@latest.

Let’s expand Server struct above with additional config timeout and maxConn.

type Server struct {
	host    string `opt`
	port    int    `opt`
	timeout time.Duration
	maxConn int
}

Then generate functional options implementation using command optgen -file server.go -name Server -w. Don’t forget use -w to append implementation to the selected file server.go. The result only generate function SetHost and SetPort only because declared opt in the struct tag just for host and port.


type Server struct {
	host    string `opt`
	port    int    `opt`
	timeout time.Duration
	maxConn int
}

func NewServer(options ...func(*Server)) (*Server, error) {

	server := &Server{}

	for _, option := range options {
		option(server)
	}

	return server, nil
}

func SetHost(host string) func(*Server) {
	return func(c *Server) {
		c.host = host
	}
}

func SetPort(port int) func(*Server) {
	return func(c *Server) {
		c.port = port
	}
}

To generate all fields as optional parameter use flag -all for example optgen -file server.go -name Server -w -all, previous generated code must be deleted manually 1

type Server struct {
	host    string `opt`
	port    int    `opt`
	timeout time.Duration
	maxConn int
}


// NewServer returns a new Server.
func NewServer(options ...func(*Server)) (*Server, error) {

	// Prepare a Server with default host.
	server := &Server{}

	// Apply options.
	for _, option := range options {
		option(server)
	}

	// Do anything here

	return server, nil
}


// SetHost sets the Host
func SetHost(host string) func(*Server) {
	return func(c *Server) {
		c.host = host
	}
}

// SetPort sets the Port
func SetPort(port int) func(*Server) {
	return func(c *Server) {
		c.port = port
	}
}

// SetTimeout sets the Timeout
func SetTimeout(timeout time.Duration) func(*Server) {
	return func(c *Server) {
		c.timeout = timeout
	}
}

// SetMaxConn sets the MaxConn
func SetMaxConn(maxconn int) func(*Server) {
	return func(c *Server) {
		c.maxConn = maxconn
	}
}
  1. The limitation optgen CLI only append code, not replace previously generated code.

programming go practical options