Defining Simple Functions

A Go function consists of a signature (or header) followed by a block of code that will be executed when the function is called.

Here’s a function that prints a greeting message:

func hello(name string) {                 // function signature
    fmt.Println("Hello " + name + "!")    // function body
}

// Calling hello("Dave") prints: Hello Dave!

This function’s name is hello, and it takes as input a single parameter of type string called name. Notice that the name of the parameter comes first, followed by its data type. It prints a message to the screen without returning a value.

In Go, function parameters are always passed by value. For example, calling f(x) means that the value of x is copied. Thus, you cannot modify the value of x, and, if x is big, the copying takes extra time and space.

Consider this function for calculating the area of a rectangle:

func rect_area(width, height int) int {  // function signature
    return width * height                // function body
}

// rect_area(10, 5) returns 50

rect_area takes two int parameters, width and height. It also returns an int: the return statement is used to end the function and return a value.

You could also have written the function with a slightly different header like this:

func rect_area(width int, height int) int {
    return width * height
}

Here’s a function that counts the number of e characters in a string:

func ecount(s string) int {
    result := 0
    for _, c := range s {
        if c == 'e' || c == 'E' {
            result++
        }
    }
    return result
}

As before, a return statement ends a function and returns a value. Another way to write this function is with a named result parameter, e.g.:

func ecount(s string) (result int) {   // result is initialized to 0
    for _, c := range s {
        if c == 'e' || c == 'E' {
            result++
        }
    }
    return                             // no need to mention result here
}

Functions can return multiple values. A common use case for this is to return a value and an error flag. For instance:

import "os/ioutil"

// ...

func ecountFile(fname string) (int, error) {   // note two return types
    bytes, err := ioutil.ReadFile(fname)       // bytes is a byte slice
    result := 0
    if err == nil {                            // err is nil when file
        for _, c := range bytes {              // is read correctly
            if c == 'e' || c == 'E' {
                result++
            }
        }
    }
    return result, err
}

error is a built-in type, and ioutil.ReadFile is a convenience function that reads an entire file from disk into a slice of bytes.

You can call ecountFile like this:

count, err := ecountFile("funcs.txt")

if err == nil {
    fmt.Println(count)
} else {
    fmt.Println("unable to read funcs.txt")
}

It’s up to the programmer to check the error flag.

We could also have written ecountFile with named result parameters:

func ecountFile(fname string) (result int, err error) {  // named results
    bytes, err := ioutil.ReadFile(fname)
    if err == nil {
        for _, c := range bytes {
            if c == 'e' || c == 'E' {
                result++
            }
        }
    }
    return
}

Questions

  1. Describe the technique Go uses to pass parameters to functions.
  2. True or false: every Go function must have an explicitly named type for its return value. For functions that don’t return a value, the return type is void.
  3. True or false: when a Go function returns more than one value, the values must be of the same type.
  4. True or false: Go lets you use both multiple return values and named result parameters in the same function.