CodeRigor

LESSON

Write functions and finish a complete program

Name a step as a function, pass values in through parameters and hand a result back with return, then write one complete interactive console program.

You have called functions since the first lesson: fmt.Println, strconv.Atoi. And you have written one in every program: main. This lesson shows how to write more functions of your own, and then puts everything from this unit into one complete program.

A function is a named step

A function is a block of statements with a name. Writing the function is called defining it. Running it is called calling it, and you can call it as many times as you like.

Go
package main

import "fmt"

func greet() {
    fmt.Println("Hello!")
    fmt.Println("Welcome to the shop.")
}

func main() {
    greet()
    greet()
}

This prints the two lines twice.

The definition starts with the word func, then the name, then round brackets, then the block in curly brackets. That is exactly the shape of main, which is a function like any other. Its one special property is that Go calls it for you when the program starts.

Three things to know:

  • Functions are defined side by side, at the top level of the file. A function is never defined inside another function.
  • Defining a function runs nothing. The program still starts at the first statement of main. The lines of greet run only when a statement calls it with greet().
  • The order of the definitions in the file does not matter. greet could be written below main.

Parameters let a function work on different values

A parameter is a variable that belongs to a function and receives a value each time the function is called. Parameters are listed in the round brackets of the definition, each with its type after its name.

Go
package main

import "fmt"

func greet(name string) {
    fmt.Println("Hello, " + name + "!")
}

func main() {
    greet("Ada")
    greet("Linus")
}

This prints Hello, Ada! and Hello, Linus!.

You met the word argument in the first lesson: it is the value written between the round brackets of a call. Here "Ada" is an argument and name is the parameter that receives it. A function with two parameters separates them with a comma, func area(width int, height int), and is called with two arguments in the same order: area(3, 4).

return hands a value back

A function that prints is useful once. A function that hands a value back to the code that called it (its caller) is useful everywhere, because the caller decides what to do with the value. The statement return does this. The type of the value handed back is written after the round brackets of the definition.

Go
package main

import "fmt"

func double(n int) int {
    return n * 2
}

func main() {
    result := double(4)
    fmt.Println(result)
    fmt.Println(double(10) + 1)
}

This prints 8 and 21.

Read the first line of the definition as: "double takes one int, called n, and gives back an int". When Go reaches a call such as double(4), it runs the function with n set to 4, and the call is then replaced by the returned value. So result := double(4) becomes result := 8, and double(10) + 1 becomes 20 + 1.

What happens during one call
start / 4 ⇄ code
The argument is copied into the parameter, the function's block runs, and the returned value takes the place of the call.
What happens during one call
The argument is copied into the parameter, the function's block runs, and the returned value takes the place of the call.

return also ends the function at once. Any statements after it in the same block do not run.

Variables created inside a function, including its parameters, exist only while that call is running. n cannot be used inside main, and a variable of main cannot be used inside double. A function receives what it needs through its parameters and hands its answer back with return.

Print or return?

These two functions look alike and are very different:

Go
package main

import "fmt"

func showDouble(n int) {
    fmt.Println(n * 2)
}

func double(n int) int {
    return n * 2
}

func main() {
    showDouble(5)
    total := double(5) + double(6)
    fmt.Println(total)
}

This prints 10 and 22. showDouble puts text on the screen and hands nothing back, so its definition has no type after the round brackets and its call cannot be used in arithmetic. double puts nothing on the screen; it produces a value that the caller can store, add, compare or print.

When a function's job is to work something out, return the result and let the caller print it.

Functions can contain everything you know

A function's block can hold variables, decisions and loops. Here a chain of if statements chooses the value to return:

Go
package main

import "fmt"

func ticketPrice(age int) int {
    if age < 5 {
        return 0
    } else if age < 16 {
        return 6
    } else {
        return 10
    }
}

func main() {
    fmt.Println(ticketPrice(3))
    fmt.Println(ticketPrice(9))
    fmt.Println(ticketPrice(40))
}

This prints 0, 6 and 10. Every path through the function ends in a return. Go insists on that for a function that promises a value.

You have already used a function that returns two values at once: strconv.Atoi hands back a number and an error value. Your own functions in this unit return one.

A complete program

Here is a small but complete interactive program: a times-table practice. It uses everything from the six lessons.

Go
package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

// verdict turns a score out of 3 into a short comment.
func verdict(score int) string {
    if score == 3 {
        return "Perfect!"
    } else if score == 2 {
        return "Nearly there."
    } else {
        return "Keep practising."
    }
}

func main() {
    scanner := bufio.NewScanner(os.Stdin)

    fmt.Print("Which times table? ")
    scanner.Scan()
    table, err := strconv.Atoi(scanner.Text())
    if err != nil {
        fmt.Println("Please type a whole number.")
        return
    }

    score := 0
    for i := 1; i <= 3; i++ {
        fmt.Printf("%d x %d = ", i, table)
        scanner.Scan()
        answer, err := strconv.Atoi(scanner.Text())
        if err != nil {
            fmt.Println("Please type a whole number.")
            return
        }
        if answer == i*table {
            fmt.Println("Correct.")
            score++
        } else {
            fmt.Println("No, it is", i*table)
        }
    }

    fmt.Printf("Score: %d out of 3\n", score)
    fmt.Println(verdict(score))
}

A session:

Text
Which times table? 7
1 x 7 = 7
Correct.
2 x 7 = 14
Correct.
3 x 7 = 20
No, it is 21
Score: 2 out of 3
Nearly there.
The parts of the times-table program
start / 4
Each part is something from an earlier lesson. The function sits outside main and is called once, at the end.
The parts of the times-table program
Each part is something from an earlier lesson. The function sits outside main and is called once, at the end.

Read the program from main, not from the top of the file. verdict has one small job, a name that says what it does, and no printing of its own. That makes it easy to check by itself: verdict(3) must be Perfect! whatever the rest of the program is doing.

Try the program in the playground with four lines in the Standard input box.

Two errors you will meet

A path with no return.

Go
package main

import "fmt"

func double(n int) int {
    result := n * 2
    fmt.Println(result)
}

func main() {
    fmt.Println(double(4))
}
Text
# command-line-arguments
./main.go:8:1: missing return

The first line of the definition promises an int, and the function reaches its closing curly bracket on line 8 without returning one. Printing a value is not returning it. End the function with return result.

An argument of the wrong type.

Go
package main

import "fmt"

func double(n int) int {
    return n * 2
}

func main() {
    fmt.Println(double("4"))
}
Text
# command-line-arguments
./main.go:10:24: cannot use "4" (untyped string constant) as int value in argument to double

The parameter n is an int, and the call on line 10 passes the string "4". Read the message from its end: "in argument to double" tells you which call, "as int value" tells you what was needed, and the start tells you what was given. Pass a number, or convert the text first.

What you can do now, and where to go next

You can define a function, give it parameters, return a value from it, and decide whether a piece of code should print or return. You can read a program made of several functions starting from main. And you have now met every part of a complete interactive console program: output, variables, input, decisions, loops and functions.

The build for this lesson is the capstone of this unit: a number-guessing game that you write as one complete program.

When you have finished it, the next step is the next unit of this course, which starts at Go values, types & zero values. From there the course moves much faster than this unit: it will not explain again what a variable or a loop is, and its examples pack more into each line. Everything it relies on, you have now practised. Expect to read its lessons slowly and to run every example, and come back here whenever a foundation needs refreshing.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Starts at 'Encapsulate Logic with Functions' (1:58:37): moving parts of a growing main function into named functions, with parameters and return values. It tidies the ticket-booking program built in earlier chapters, so some of the code being moved uses features this unit has not covered.

Starts at 'Ch 3. Functions' (51:11): defining functions, parameter types and return values in short exercises. The later part of the chapter covers extras such as several return values, which you can leave for the next unit.