golang · level 3

Functions

Multi-return, variadic, methods.

100 XP

Go Functions

Functions are first-class values in Go. Multiple return values are routine — and the foundation of Go's error handling.

Declaring a function

func add(a int, b int) int {
    return a + b
}

// When consecutive params share a type, declare it once:
func add2(a, b int) int {
    return a + b
}

The parameter list is name type, opposite of C and Java. Same for the return type, which goes after the parameter list.

Multiple return values

A Go function can return more than one value. The convention everyone follows: the last return value is an error when an operation can fail.

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("divide by zero")
    }
    return a / b, nil
}

result, err := divide(10, 0)
if err != nil {
    log.Fatal(err)
}

If you don't need a return value, use the blank identifier _:

_, err := divide(10, 2)

Named return values

You can name the return values in the signature. They're declared as variables in the function body, with their zero values:

func split(sum int) (x, y int) {
    x = sum * 4 / 9
    y = sum - x
    return    // "naked" return — uses the named values
}

Reads OK for short helpers; gets confusing in longer functions. Most Go code declares them locally and returns them explicitly.

Variadic functions

A function can accept any number of trailing arguments via ...:

func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

sum(1, 2, 3, 4)            // 10

// To pass a slice as variadic args, "unpack" with ...:
nums := []int{1, 2, 3, 4}
sum(nums...)

fmt.Printf and fmt.Println are variadic — that's why you can pass arbitrary numbers of args.

Functions as values

func apply(f func(int) int, x int) int {
    return f(x)
}

apply(func(n int) int { return n * 2 }, 5)    // 10

Anonymous functions ("closures") capture variables from the surrounding scope:

func makeCounter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

c := makeCounter()
c()          // 1
c()          // 2
c()          // 3

Methods are functions with a receiver

A method is a function with a "receiver" — the type it's attached to:

type Money struct {
    Cents int
}

// Value receiver: gets a copy.
func (m Money) Dollars() float64 {
    return float64(m.Cents) / 100
}

// Pointer receiver: can mutate the original.
func (m *Money) Add(other Money) {
    m.Cents += other.Cents
}

m := Money{100}
m.Dollars()                // 1.0
m.Add(Money{50})
m.Cents                    // 150

Use a pointer receiver when the method needs to mutate the receiver, OR when the receiver is large and copying would be expensive.

init

Each file can have an init() function that runs once when the package is loaded — before main():

func init() {
    seed := time.Now().UnixNano()
    rand.Seed(seed)
}

Use sparingly. Implicit setup is hard to test and easy to miss.

Naming

  • Exported names start with a capital letter (PublicThing).
  • Unexported names start with lowercase (internalHelper).
  • The visibility is built into the name itself; there's no public keyword.
package user

func Create(name string) *User { ... }   // exported — visible from other packages
func validate(name string) bool { ... }  // unexported — package-private