Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equivalent of optional parameters in Go

Tags:

go

I am porting a client to an API from python. The original implementation uses optional parameters as

def createIntAttribute(attr_name, default_value=None, is_array=False)

which create an integer attribute on database with or without a default value as in:

createIntAttribute("numUsers")             # No default attribute
createIntAttribute("numUsers", 0)          # Default attribute of 0

How can this be implemented in Go?

Some approaches I have considered are:

  • Using int as type
    There's no way to know if user wanted to avoid creating a default value or wanted to create a default of 0
  • Using a pointer defaultValue *int
    Needs & on every use and doesn't support literals
  • Using a struct for parameters
    The default value for int is 0 so same problem as first approach
  • Using a struct with value and isSet attributes to enclose optional parameters
    Needs a struct for each type of variable. This can probably use generics but adds a requirement of go version > 1.18
    usage is mypackage.NewOptionalStringArray([]string{"hello", "world"})
  • Using variadic function func createIntAttribute(name string, optionals ...interface{})
    No typechecking by default
    Autocomplete doesn't show name and type of variable; using individual argument with vs code shows the name of argument and its type as I am typing them

What would be the best way to implement this?

like image 531
Nevus Avatar asked Sep 16 '25 22:09

Nevus


1 Answers

One approach is to use the Functional Options Pattern. See the demo below:

package main

import (
    "log"
)

type options struct {
    hasDefault   bool
    defaultValue int
}

type option func(*options)

func withDefault(value int) option {
    return func(o *options) {
        o.hasDefault = true
        o.defaultValue = value
    }
}

func createIntAttribute(name string, setters ...option) {
    o := &options{}

    for _, setter := range setters {
        setter(o)
    }

    log.Println(name, o)
}

func main() {
    createIntAttribute("test1")
    createIntAttribute("test1", withDefault(10))
}

This approach can be more user friendly (maybe) when implemented as method chain:

package main

import (
    "log"
)

type createIntAttributeParams struct {
    name         string
    hasDefault   bool
    defaultValue int
}

// mandatory parameters here
func createIntAttribute(name string) *createIntAttributeParams {
    return &createIntAttributeParams{
        name: name,
    }
}

// optional parameter
func (p *createIntAttributeParams) withDefault(value int) *createIntAttributeParams {
    p.hasDefault = true
    p.defaultValue = value
    return p
}

// other with* functions to set more optional parameters

// execute
func (p *createIntAttributeParams) do() {
    log.Println(*p)
}

func main() {
    createIntAttribute("test1").do()
    createIntAttribute("test1").withDefault(10).do()
}
like image 128
Zeke Lu Avatar answered Sep 18 '25 16:09

Zeke Lu