Home > Blockchain >  Golang alternative to c function with default params: multiple functions, or struct param
Golang alternative to c function with default params: multiple functions, or struct param

Time:09-22

I would like to know the best practice in Go equivalent to C functions binding with default params, which may be easiest for the user to see the function params(with linter help).
What do you think will be the most GO style and easiest way to use the test function ?

An example function in C :

void test(int x, int y=0, color=Color());

Equivalence in Go

1. With multiple signatures:

func test(x int)
func testWithY(x int, y int)
func testWithColor(x int, color Color)
func testWithYColor(x int, y int, color Color)

pro:

  • The linter will show all the possibilities for test
  • The compiler will take the shortest path

cons:

  • Can be overwhelmed when there is a lots of params

2. With struct parameter:

type testOptions struct {
    X      int
    Y      int
    color  Color
}

func test(opt *testOptions)

// user 
test(&testOptions{x: 5})

pro:

  • Only one signature
  • Can specify only some values

cons:

  • Need to define a struct
  • The values will be set by default by the system

With the help of the module github.com/creasty/defaults, there is a way to set default values (but with the cost of calling reflect in runtime).

type testOptions struct {
    X      int
    Y      int `default:"10"`
    color  Color `default:"{}"`
}

func test(opt *testOptions) *hg.Node {
    if err := defaults.Set(opt); err != nil {
        panic(err)
    }
}

pro:

  • set default values

cons:

  • Use of reflect in runtime

P.S.: I saw the use of variadic parameters ... or/with interface{} but I find it not easy to know which params to use (or maybe there is a way to indicate a params list to the linter).

CodePudding user response:

Either way will work fine, but in Go the functional options pattern might be more idiomatic for implementing such functionality.

It is based on the idea of accepting a variable amount of WithXXX type of functional arguments that extend of modify the behavior of the call.

type Test struct {
    X     int
    Y     int
    color Color
}

type TestOption func(*Test)

func test(x int, opts ...TestOption) {
    p := &Test{
        X: x,
        Y: 12,
        Color: defaultColor,
    }
    for _, opt := range opts {
        opt(p)
    }
    p.runTest()
}

func main() {
    test(12)
    test(12, WithY(34))
    test(12, WithY(34), WithColor(Color{1, 2, 3}))
}

func WithY(y int) TestOption {
    return func(p *Test) {
        p.Y = y
    }
}

func WithColor(c Color) TestOption {
    return func(p *Test) {
        p.color = c
    }
}

CodePudding user response:

I think that your option 1 is a good idiomatic choice if the result is only a handful of functions. I also think the functional options pattern is a fine choice, it is often used in factory functions.

To add another choice, this question might be an indication that the code needs to be re-factored. Should test() be a method on a type which knows the optional parameters?

One of the go proverbs is "make the zero value useful", so a type with its zero value is assumed to mean use the default. If 0 is a valid value for your int type then consider *int, the point is to avoid referencing fields where you want to keep the default.

package main

import (
    "fmt"
)

const (
    defaultY     = 10
    defaultColor = "blue"
)

type Color string

type Thing struct {
    Y     int
    Color Color
}

func (t Thing) getY() int {
    if t.Y == 0 {
        return defaultY
    }
    return t.Y
}

func (t Thing) getColor() Color {
    if t.Color == "" {
        return defaultColor
    }
    return t.Color
}

func (t Thing) Test(x int) {
    fmt.Println(x, t.getY(), t.getColor())
}

func main() {
    Thing{}.Test(12)
    Thing{Y: 11}.Test(12)
    Thing{Y: 11, Color: "red"}.Test(12)
}

// 12 10 blue
// 12 11 blue
// 12 11 red

  • Related