Practical Go: Functional Options Pattern

Practical Go: Functional Options Pattern

January 20, 2023

Moch Lutfi
Name
Moch Lutfi
Twitter
@kaptenupi

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

main.go
server.go

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)
}
}

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.

server.go

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.

server.go

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

server.go

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
}
}

Footnotes

  1. The limitation optgen CLI only append code, not replace previously generated code.