Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Optional parameters with defaults in Go struct constructors

I've found myself using the following pattern as a way to get optional parameters with defaults in Go struct constructors:

package main

import (
    "fmt"
)

type Object struct {
    Type int
    Name string
}

func NewObject(obj *Object) *Object {
    if obj == nil {
        obj = &Object{}
    }
    // Type has a default of 1
    if obj.Type == 0 {
        obj.Type = 1
    }
    return obj
}

func main() {
    // create object with Name="foo" and Type=1
    obj1 := NewObject(&Object{Name: "foo"})
    fmt.Println(obj1)

    // create object with Name="" and Type=1
    obj2 := NewObject(nil)
    fmt.Println(obj2)

    // create object with Name="bar" and Type=2
    obj3 := NewObject(&Object{Type: 2, Name: "foo"})
    fmt.Println(obj3)
}

Is there a better way of allowing for optional parameters with defaults?

like image 508
Robert Kajic Avatar asked Nov 22 '13 17:11

Robert Kajic


People also ask

Can we use optional parameters in a constructor?

The definition of a method, constructor, indexer, or delegate can specify its parameters are required or optional. Any call must provide arguments for all required parameters, but can omit arguments for optional parameters. Each optional parameter has a default value as part of its definition.

How do you pass an optional parameter in Go?

Go doesn't support optional function parameters. However, the need for optional parameters will always exist. There are many ways to provide optional parameters to a function in Go, but the most graceful way is to use functional options.

Why are optional parameters bad?

The thing with optional parameters is, they are BAD because they are unintuitive - meaning they do NOT behave the way you would expect it. Here's why: They break ABI compatibility ! so you can change the default-arguments at one place.

What are default and optional parameters?

The default value of an optional parameter is a constant expression. The optional parameters are always defined at the end of the parameter list. Or in other words, the last parameter of the method, constructor, etc. is the optional parameter.


2 Answers

Dave Cheney offered a nice solution to this where you have functional options to overwrite defaults:

https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis

So your code would become:

package main

import (
    "fmt"
)

type Object struct {
    Type int
    Name string
}

func NewObject(options ...func(*Object)) *Object {
    // Setup object with defaults 
    obj := &Object{Type: 1}
    // Apply options if there are any
    for _, option := range options {
        option(obj)
    }
    return obj
}

func WithName(name string) func(*Object) {
    return func(obj *Object) {
        obj.Name = name
    }
}

func WithType(newType int) func(*Object) {
    return func(obj *Object) {
        obj.Type = newType
    }
}

func main() {
    // create object with Name="foo" and Type=1
    obj1 := NewObject(WithName("foo"))
    fmt.Println(obj1)

    // create object with Name="" and Type=1
    obj2 := NewObject()
    fmt.Println(obj2)

    // create object with Name="bar" and Type=2
    obj3 := NewObject(WithType(2), WithName("foo"))
    fmt.Println(obj3)
}

https://play.golang.org/p/pGi90d1eI52

like image 196
Iain Duncan Avatar answered Jan 01 '23 11:01

Iain Duncan


The approach seems reasonable to me. However, you have a bug. If I explicitly set Type to 0, it will get switched to 1.

My suggested fix: Use a struct literal for the default value: http://play.golang.org/p/KDNUauy6Ie

Or perhaps extract it out: http://play.golang.org/p/QpY2Ymze3b

like image 35
Tyler Avatar answered Jan 01 '23 11:01

Tyler