@reiver

Function and Method Overloading in Golang

TL;DR: Use a variadic function or method, possibly of type ...interface{} with defaulting and validation in the function or method body.

There are 4 things you may have thought we impossible in Golang. Perhaps even verboten.

But I am here to tell you that not only are they (more or less) possible, but that you can use them in your Golang code today (if you really want to).

Those 4 things are:

  1. function overloading,
  2. method overloading,
  3. optional parameters,
  4. default values for parameters.

But there is a trick to using them. In this article I explain the trick.

But first let's take a look at what each of these looks like. Just to make sure we are "on the same page".

Function Overloading

Function overloading, from a programmer's point-of-view, let us call the "same" function with different parameters.

For example:


x1 := MyFunc()

x2 := MyFunc(5)

x3 := MyFunc(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

Figure 1. An example of function overloading in Golang.

Method Overloading

Method overloading is very similar to function overloading, except instead of overloading a function we are overloading a method. In other words ….

Method overloading, from a programmer's point-of-view, let us call the "same" method with different parameters.

For example:


y1 := thing.MyMethod()

y2 := thing.MyMethod(5)

y3 := thing.MyMethod(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

Figure 2. An example of method overloading in Golang.

Optional Parameters

We actually already saw instances of optional parameters with both the functional overloading and method overloading examples in figure 1 and figure 2. But let's be more specific ….

Optional parameters, from a programmer's point-of-view, is a parameter of a function or method that we do not have to specify.

For example:


// There are 2 parameters here.
z1 := CherryFunc(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

// Here we leave off the 2nd parameter.
// That makes the 2nd parameter an optional parameter.
z2 := CherryFunc(5)

Figure 3. An example of an optional parameter on a function in Golang.

And, since the last example showed optional parameters for a function, here is an example with optional parameters for a method:


// There are 2 parameters here.
z1 := apple.BananaMethod(5, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")

// Here we leave off the 2nd parameter.
// That makes the 2nd parameter an optional parameter.
z2 := apple.BananaMethod(5)

Figure 4. An example of an optional parameter on a method in Golang.

Default Values for Parameters

Again, we have already seen what default values for parameters looks like from a programmer's point-of-view, with our previous examples in figure 1, figure 2, figure 3 and figure 4. But let's be more specific ….

Default values for parameters, are the value an optional parameter gets when it is not specified.

There no point in show yet another example for this. figure 1, figure 2, figure 3 and figure 4 allow illustrate what this looks like from a programmer's point-of-view.

Officially

So, just to be clear, this is what the Golang FAQ says on function overloading at the time I wrote this article:

Why does Go not support overloading of methods and operators?

Method dispatch is simplified if it doesn't need to do type matching as well. Experience with other languages told us that having a variety of methods with the same name but different signatures was occasionally useful but that it could also be confusing and fragile in practice. Matching only by name and requiring consistency in the types was a major simplifying decision in Go's type system.

Technically they are correct. But like I said, there is a trick that can (more or less) let you do function and method overloading in Golang. At least from the programmer's point-of-view. Even if the compiler doesn't think of it an function overloading.

So let's start digging into the trick.

And really, there are 2 parts to the trick!

Variadic

The 1st part of this 2 part trick is to use a variadic function. (The 2nd part will be described later, after we get through this.)

Golang supports variadic functions and methods. (You may have also heard this named "varargs".)

If you don't already know what variadic means, then ….

A variadic function or method is a function or method that accepts a variable number of parameters.

So, for example:


p1 := MultiplyAll(1, 2, 3)

p2 := MultiplyAll(153, 196883, 1729, 1634, 5, 36)

p3 := MultiplyAll(1, -2)

p4 := MultiplyAll()

Figure 5. An example where a variadic function is being called with different parameter counts.

And actually, you are probably already familiar with at least one variadic function in Golang. Namely, you are probably already familiar with at least one of these functions: fmt.Print(), fmt.Printf() or fmt.Println().

For example:


name := "Joe Bloe"
age  := 23
city := "Vancouver"

// This call to fmt.Printf() has 4 parameters.
fmt.Printf("%s is %d years old and lives in %s.\n", name, age, city)

// This call to fmt.Printf() has 2 parameters.
fmt.Printf("Hello %s!\n", name)

Figure 6. An example where the standard Golang variadic function fmt.Printf(), is being call with different parameter counts.

Writing Your Own Variadic Functions and Methods

That kind of code isn't magic. You can write your own variadic functions and methods.

Here is an example:


package main

import "fmt"

// Sum returns the sum of adding all the numbers in
// the parameters together.
func Sum(numbers ...int) int {

    n := 0

    for _,number := range numbers {
        n += number
    }

    return n
}

func main() {
    sm1 := Sum(1, 2, 3, 4) // = 1 + 2 + 3 + 4 = 10

    sm2 := Sum(1, 2) // = 1 + 2 = 3

    sm3 := Sum(7, 1, -2, 0, 18) // = 7 + 1 + -2 + 0 + 18 = 24

    sm4 := Sum() // = 0


    fmt.Printf("sm1 = %d\n", sm1)
    fmt.Printf("sm2 = %d\n", sm2)
    fmt.Printf("sm3 = %d\n", sm3)
    fmt.Printf("sm4 = %d\n", sm4)

    // Output:
    // sm1 = 10
    // sm2 = 3
    // sm3 = 24
    // sm4 = 0
}

Figure 7. An example variadic function.

One of the important parts in there to note is how you declare the parameters in a variadic function in Golang.

The 1st thing to note is that, even though the variadic function accepts a variable number of parameters, in the code we actually only have 1 parameter. In our example, we named that single parameter: numbers. (We could have named it anything though.)

The 2nd thing to note is that weird notation for the type of that 1 parameter: ...int. That ...int means that our variadic function accepts a variable number of int parameters.

Also, in case you were wondering, the "real" type of numbers in our example ends up being []int. (But of course, in our code we have to put ...int and not []int to tell Golang that this is a variadic function.)

Let's look at one more variadic function example:


package main

import "fmt"
import "github.com/reiver/go-stringcase"

// CamelCaseAll returns a []string that camelCases every string passed
// as a parameter.
func CamelCaseAll(ss ...string) []string {

    camelCasedStrings := make([]string, len(ss))

    for i,s := range ss {
        camelCasedStrings[i] = stringcase.ToCamelCase(s)
    }

    return camelCasedStrings
}

func main() {
    ss1 := CamelCaseAll("hello world", "Apple Banana Cherry")
    // = []string{"helloWorld", "appleBananaCherry"}

    ss2 := CamelCaseAll("More than meets the eye")
    // = []string{"moreThanMeetsTheEye"}

    ss3 := CamelCaseAll()
    // = []string{}


    fmt.Printf("ss1 = %v\n", ss1)
    fmt.Printf("ss2 = %v\n", ss2)
    fmt.Printf("ss3 = %v\n", ss3)

    // Output:
    // ss1 = [helloWorld appleBananaCherry]
    // ss2 = [moreThanMeetsTheEye]
    // ss3 = []
}

Figure 8. Another example variadic function.

This time in our variadic function, our parameter has a type of ...string (rather than ...int).

...interface{}

So, the astute reader may have already noticed that the examples in figure 7 and figure 8 have one limitation.

That limitation is that all the parameter are of the same type.

In the example in figure 7 ...int meant that all the parameters were of type int. And in the example in figure 8 ...string meant that all the parameters we of type string.

What we really want is to be able to have different types passed to a variadic function.

As it turns out, we can get past this one limitation!

And we do that by using interface{}. Or more specifically, using ...interface{}.

Let's take a look at an example:


package main

import "fmt"

// Ul outputs HTML code with each parameter as a list item.
func Ul(things ...interface{}) {

    fmt.Println("<ul>")

    for _,it := range things {
        fmt.Printf("    <li>%v</li>\n", it)
    }

    fmt.Println("</ul>")
}

func main() {
    // 2 parameter. 1st one a string. 2nd one an int.
    Ul("Hello world", 123)

    // 5 parameters. 1st, 3rd and 5th a string. 2nd a float64. 4th a int.
    Ul("apple", 7.2, "BANANA", 5, "cHeRy")

    // Output:
    // <ul>
    //     <li>Hello world</li>
    //     <li>123</li>
    // </ul>
    // <ul>
    //     <li>apple</li>
    //     <li>7.2</li>
    //     <li>BANANA</li>
    //     <li>5</li>
    //     <li>cHeRy</li>
    // </ul>

}

Figure 9. An example variadic function that uses ...interface{}. Note that this code, if it were used in production, should be doing some HTML escaping. But it doesn't so that what the code is trying demonstrate is clearer.

This is the 2nd part of this 2 part trick. I.e., make your variadic function be of type ...interface{}.

Defaulting Parameters, Optional Parameters, Validating Parameters

So, now that you know the ...interface{} trick, you have everything you need and can figure out the rest of the details yourself.

But let me show you an example of having optional parameters with defaults an some validation.

So, conceptually, what you want to do is this:


// THIS IS NOT VALID GOLANG CODE!!!!!
//
// The 1st parameter is mandatory.
//
// The 2nd, 3rd and 4th parameters are optional and have default values.
func Player(name string, x int = 0, y int = 0, color string = "green") {
    // ...
}

Figure 10. This is not valid Golang code. This is meant illustrate what we conceptually want to do with the defaulting of parameters.

Now before I show you one way you can implement what we conceptually want to do in the code example in figure 10, let me say that the code example (in figure 11) is a bit vebose.

But later on I offer an alternative for making that less verbose. But seeing this first I hope aids in understanding what is going on.

So, having said that this is one way we can implement what we conceptually want to do in figure 10:


func Player(args ...interface{}) {

    // Mandatory parameters.
    var name string              // ← We did NOT (explicitly) intialize name.

    // Optional parameters.
    //
    // We initialize each of these to their default value.
    var x int = 0                // ← We initialize x to zero (0) here.
    var y int = 0                // ← We initialize y to zero (0) here.
    var color string = "green"   // ← We initialize color to "green" here.

    // Since we have 1 mandatory parameter, make sure the number of parameters
    // we have is at least 1.
    //
    // We can figure out the number of parameters passed to us with len(args).
    if 1 > len(args) {
        panic("Not enough parameters.")
    }


    // Get any parameters passed to us out of the args variable into "real"
    // variables we created for them.
    for i,p := range args {
        switch i {
            case 0: // name
                param, ok := p.(string)
                if !ok {
                    panic("1st parameter not type string.")
                }
                name = param

            case 1: // x
                param, ok := p.(int)
                if !ok {
                    panic("2nd parameter not type int.")
                }
                x = param

            case 2: // y
                param, ok := p.(int)
                if !ok {
                    panic("2nd parameter not type int.")
                }
                y = param

            case 3: // color
                param, ok := p.(string)
                if !ok {
                    panic("3rd parameter not type string.")
                }
                color = param

            default:
                panic("Wrong parameter count.")
        }
    }

    // ...
}

Figure 11. One way of implenting what we conceptually want for parameter defaulting in figure 10.

This is just one way you can do that.

And although this example lacks syntactic sugar, you could move some of that code into another library or into another function, to make it nicer to look at.

Perhaps:


func Player(args ...interface{}) {

    name, x, y, color, err := playerParams(args...)
    if nil != err {
        panic(err.Error())
    }

    // ...
}

func playerParams(args ...interface{}) (name string, x int, y int, color string, err error) {

    // We initialize each of the optional parameters to their default value.
    x = 0            // ← We initialize x to zero (0) here.
    y = 0            // ← We initialize y to zero (0) here.
    color = "green"  // ← We initialize color to "green" here.

    // Since we have 1 mandatory parameter, make sure the number of parameters
    // we have is at least 1.
    //
    // We can figure out the number of parameters passed to us with len(args).
    if 1 > len(args) {
        err = errors.New("Not enough parameters.")
        return
    }


    // Get any parameters passed to us out of the args variable into "real"
    // variables we created for them.
    for i,p := range args {
        switch i {
            case 0: // name
                param, ok := p.(string)
                if !ok {
                    err = errors.New("1st parameter not type string.")
                    return
                }
                name = param

            case 1: // x
                param, ok := p.(int)
                if !ok {
                    err = errors.New("2nd parameter not type int.")
                    return
                }
                x = param

            case 2: // y
                param, ok := p.(int)
                if !ok {
                    err = errors.New("2nd parameter not type int.")
                    return
                }
                y = param

            case 3: // color
                param, ok := p.(string)
                if !ok {
                    err = errors.New("3rd parameter not type string.")
                    return
                }
                color = param

            default:
                err = errors.New("Wrong parameter count.")
                return
        }
    }


    return
}

Figure 12. Another way of implenting what we conceptually want for parameter defaulting in figure 10.

To make this easier, you could even use a custom tool that integrates with "go generate". Perhaps a tool that works similar to Matt Sherman[tw, gh]'s gen to automate the creation of the parameter extraction, defaulting and validation code.

To get something like:


// @param name string
// @param x int 0
// @param y int 0
// @param color int "green"
func Player(args ...interface{}) {

    // NOTE that the function playerParams() is autogenerated.
    name, x, y, color, err := playerParams(args...)
    if nil != err {
        panic(err.Error())
    }

    // ...
}

Figure 13. Yet another way of implenting what we conceptually want for parameter defaulting in figure 10.

Summary

So, if you read through all of that, then (hopefully) you now know how to use ...interface{} to effectively be able to do function overloading and method overloading in Golang.

But, at least for now, there is some extra work for the programmer to do, when using this trick as there is less syntactic sugar available for the person writing the variadic function or method using ...interface{}.

Well, until someone creates a tool like the one I suggested in figure 13. (I may do it if I have the time.)

Caveats

But it is important to be aware of the cost to this.

We have lost compile time type checking of parameters.

In loosingthat, and I think this is an important one, the defaulting and validation needs to be done at runtime.

But regarless, if you need to do something like function overloading and method overloading in Golang, you can do it like this.

--