21. Functions and errors
You have called functions since Chapter 07: fmt.Println,
strings.ToUpper, strconv.Atoi,
rand.Intn. Now you write your own. A function names a chunk
of work you can reuse, which is the named step from Chapter 05 turned
into code.
This chapter also keeps a promise from Part 3. It explains the second
value from strconv.Atoi and ReadString, the
one you have been throwing away with _.
Defining and calling
The simplest definition:
func greet() {
fmt.Println("Hello!")
}
func main() {
greet() // Hello!
greet() // Hello!
}Read it as: define a function greet that takes
nothing and runs its body when called. func starts the
definition, the () holds what it takes, and the body sits
between { and }. Call it by writing its name
with ().
Now you can see what the frame from Chapter 07 was.
func main() { ... } is an ordinary function definition, and
main is the one Go calls for you when the program starts.
Every other function runs only when something calls it.
From here on, code blocks that define functions show
main too, but still leave out the package and
import lines.
Functions go outside main, at the top
level of the file, and their order does not matter: main
can call a function defined further down. A function inside
main does not compile:
func main() {
func greet() {
fmt.Println("Hello!")
}
greet()
}syntax error: unexpected greet, expected (
syntax error: non-declaration statement outside function body
The second line is only a result of the first. Move
greet out of main, and both go away.
Parameters
A function takes inputs as parameters in the parentheses. Each one has a name and a type:
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
greet("Keiko") // Hello, Keiko!
greet("Arif") // Hello, Arif!
}name lives for one call, and the caller supplies its
value, which is called the argument. With more than one
parameter, separate them with commas:
func add(a int, b int) {
fmt.Printf("%d + %d = %d\n", a, b, a+b)
}
func main() {
add(3, 4) // 3 + 4 = 7
}When neighbouring parameters have the same type, Go lets you write the type once. You will see this in other people's code, and it means the same thing:
func add(a, b int) {The types are checked before the program runs. Passing the wrong type stops with an error like the ones in Chapter 11:
func add(a int, b int) {
fmt.Printf("%d + %d = %d\n", a, b, a+b)
}
func main() {
add(3, "four")
}cannot use "four" (untyped string constant) as int value in argument to add
Every argument is required
Go has no optional parameters and no default values for them. A call must give exactly the arguments the function asks for. Too few:
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
greet()
}not enough arguments in call to greet
have ()
want (string)
Too many stops in the same way, and the message says
too many arguments, with the same have and
want lines to compare:
too many arguments in call to greet
have (string, string)
want (string)
If you want a fallback value, write it yourself. A parameter is a variable inside the function, so the check from Chapter 17 works on it. A second function with a clear name can then supply the value:
func greet(name string) {
if name == "" {
name = "World"
}
fmt.Printf("Hello, %s!\n", name)
}
func greetEveryone() {
greet("")
}
func main() {
greet("Keiko") // Hello, Keiko!
greetEveryone() // Hello, World!
}return: handing a
value back
To produce a value instead of printing, use
return, and write the type of the value after the
parentheses:
func square(n int) int {
return n * n
}
func main() {
result := square(5)
fmt.Println(result) // 25
fmt.Println(square(7)) // 49
fmt.Println(square(3) + 1) // 10
}return ends the function at once and hands the value
back to the caller. A function that promises a value must reach a
return on every path. Otherwise:
func isPositive(n int) bool {
if n > 0 {
return true
}
}
func main() {
fmt.Println(isPositive(5))
}missing return
Go reports it at the closing }, because that is where
the function could end with no value.
A function with no type after its parentheses gives back nothing, and Go will not let you use its result as a value:
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func main() {
result := greet("Keiko")
fmt.Println(result)
}greet("Keiko") (no value) used as value
Open exercises/21/01-square.go. Add a function
cube that returns n * n * n, then print
cube(3).
Multiple return values
A Go function can give back several values at once. List their types
in parentheses, and separate the values with commas in the
return:
func divide(a int, b int) (int, int) {
return a / b, a % b
}
func main() {
quotient, remainder := divide(17, 5)
fmt.Println(quotient, remainder) // 3 2
}You have been on the receiving end of this since Chapter 14, when
strconv.Atoi gave you two values. It is the same
two-variable assignment you met in Chapter 11. You must take all of
them, because one variable cannot hold two values:
func divide(a int, b int) (int, int) {
return a / b, a % b
}
func main() {
result := divide(17, 5)
fmt.Println(result)
}assignment mismatch: 1 variable but divide returns 2 values
If you want only some of them, put _ in place of the
others. That is all the _ in text, _ := ever
did:
func divide(a int, b int) (int, int) {
return a / b, a % b
}
func main() {
_, remainder := divide(20, 6)
fmt.Println(remainder) // 2
}This is exercises/21/02-divmod.go.
Early return for
guard checks
Since return ends the function at once, it is handy for
the easy cases first:
func absolute(n int) int {
if n >= 0 {
return n
}
return -n
}
func main() {
fmt.Println(absolute(5)) // 5
fmt.Println(absolute(-7)) // 7
}No else is needed. If the first branch matches, the
function exits there, and the line after the if never runs.
This is a guard, or early return.
Scope: where variables live
A variable created inside a function lives only for that call. Once the function returns, it is gone:
func counterStep() int {
count := 0
count = count + 1
return count
}
func main() {
fmt.Println(counterStep()) // 1
fmt.Println(counterStep()) // 1
fmt.Println(counterStep()) // 1
}Every call gets a fresh count. Parameters work the same
way: changing one inside a function does not change the caller's
variable. Other functions cannot see it either:
func setUp() {
count := 0
count = count + 1
}
func main() {
setUp()
fmt.Println(count)
}undefined: count
To keep a value between calls, declare it outside every function. All the functions in the file can then use it:
var steps = 0
func step() int {
steps = steps + 1
return steps
}
func main() {
fmt.Println(step()) // 1
fmt.Println(step()) // 2
fmt.Println(step()) // 3
}Chapter 11 said that := only works inside a function,
and this is where that matters. Outside a function, you must write
var. exercises/21/04-outside-function.go has
this mistake on purpose, and running it stops with:
# command-line-arguments
exercises\21\04-outside-function.go:11:1: syntax error: non-declaration statement outside function body
Use a variable outside the functions when you need to. Most data is
better passed in as parameters and handed back with return,
because then you can see from the call what a function reads and
changes. Chapter 26 shows a cleaner way to keep related data together.
These examples are in exercises/21/03-counter-scope.go.
Errors: the second value
strconv.Atoi gives back two values: the number, and a
report of whether the conversion worked. That report has the type
error. It holds one of two things:
nil, a Go word meaning "nothing here",
when all went well, or a description of the problem. Keep it in a
variable, usually called err, and check it on the very next
line:
number, err := strconv.Atoi(text)
if err != nil {
fmt.Println("That is not a whole number.")
return
}Read err != nil as "there is an error". This is
exercises/21/05-check-atoi.go, inside
main:
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter a whole number: ")
text, _ := reader.ReadString('\n')
number, err := strconv.Atoi(strings.TrimSpace(text))
if err != nil {
fmt.Println("That is not a whole number.")
fmt.Println("The error says:", err)
return
}
fmt.Println("You typed the number", number)A return inside main ends the program.
Three runs, typing 0, then abc, then only
Enter:
Enter a whole number: 0
You typed the number 0
Enter a whole number: abc
That is not a whole number.
The error says: strconv.Atoi: parsing "abc": invalid syntax
Enter a whole number:
That is not a whole number.
The error says: strconv.Atoi: parsing "": invalid syntax
This fixes the problem from Chapters 14 and 17: a real 0
and bad text no longer look the same, because you ask err
and not the number. When err is not nil, the
number is not an answer, so do not use it.
strconv.ParseFloat works the same way:
x, err := strconv.ParseFloat("abc", 64)
fmt.Println(x) // 0
fmt.Println(err) // strconv.ParseFloat: parsing "abc": invalid syntaxThe err also catches the exception Chapter 15 mentioned,
a whole number too large for an int:
n, err := strconv.Atoi("99999999999999999999")
fmt.Println(n) // 9223372036854775807
fmt.Println(err) // strconv.Atoi: parsing "99999999999999999999": value out of rangeGo will not let you forget about err by accident. A
variable you never use stops the program from compiling, as Chapter 10
showed, so you either check err or throw it away on purpose
with _:
number, err := strconv.Atoi("42")
fmt.Println(number)err declared and not used
Returning your own errors
Your own functions can report a problem in the same way. Add
error as the last return type. On failure, return an error
made with errors.New, from the errors package.
On success, return nil:
func safeDivide(a int, b int) (int, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}When a value must go first, 0 fills its place on
failure. To put values into the message, use fmt.Errorf,
which takes the same verbs as fmt.Sprintf. A function that
has only a problem to report returns just the error:
func checkLevel(level int) error {
if level < 1 || level > 99 {
return fmt.Errorf("level %d is not from 1 to 99", level)
}
return nil
}The caller checks at once, as it does with strconv.Atoi.
exercises/21/06-your-own-errors.go calls both, and
prints:
Result: 3
Error: cannot divide by zero
<nil>
level 150 is not from 1 to 99
The last two lines show the two things an error can
hold. nil prints as <nil>.
Open exercises/21/06-your-own-errors.go. Add a function
checkAge that returns an error when its argument is below
0, and nil otherwise. Call it from main with
both kinds of value.
Noticing that the input has run out
Chapter 20 warned that a validation loop prints its message forever when the input runs out. This happens when input comes from a file instead of the keyboard. At the keyboard, on Windows, pressing Ctrl + Z and then Enter ends the input in the same way.
The second value from ReadString reports this. It is an
error that is not nil when there is no more to
read:
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
fmt.Println("No more input.")
break
}The check has two parts because the last line of a file may have no
line ending. ReadString then gives you that text
and an error together, and the text is still good. Only an
error with no text means that the input has run out.
This is exercises/21/07-read-until-end.go, inside
main:
reader := bufio.NewReader(os.Stdin)
total := 0
for {
fmt.Print("Number: ")
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
fmt.Println("No more input.")
break
}
number, err := strconv.Atoi(line)
if err != nil {
fmt.Println("Skipped:", line)
continue
}
total += number
}
fmt.Println("Total:", total)A session, where the input runs out after the 7:
Number: 5
Number: abc
Skipped: abc
Number: 7
Number: No more input.
Total: 12
Look at number, err := strconv.Atoi(line).
err already exists in this round, but number
is new, so := is allowed, and err is simply
reused. It is the same rule as in Chapter 15.
Now the validation loop can be finished. Chapter 20 said its range
check could not accept 0 as a good answer. With
err, it can, and the loop stops when the input runs out.
This is exercises/21/09-validate-volume.go, inside
main:
reader := bufio.NewReader(os.Stdin)
var volume int
for {
fmt.Print("Volume (0 to 10): ")
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
fmt.Println("No more input.")
return
}
volume, err = strconv.Atoi(line)
if err == nil && volume >= 0 && volume <= 10 {
break
}
fmt.Println("That is not a volume from 0 to 10.")
}
fmt.Println("Volume set to", volume)A session:
Volume (0 to 10): abc
That is not a volume from 0 to 10.
Volume (0 to 10): 150
That is not a volume from 0 to 10.
Volume (0 to 10): 0
Volume set to 0
Putting the reading lines in a function
Chapter 15 repeated the reading lines for each value, and promised a
better way. Here it is, in
exercises/21/08-read-functions.go:
// Chapter 21 example: the input-reading lines, in functions of their own.
// Run with: go run exercises/21/08-read-functions.go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
var reader = bufio.NewReader(os.Stdin)
// readLine prints the prompt and gives back one trimmed line. The error is
// not nil when there is no more input.
func readLine(prompt string) (string, error) {
fmt.Print(prompt)
text, err := reader.ReadString('\n')
line := strings.TrimSpace(text)
if err != nil && line == "" {
return "", err
}
return line, nil
}
// readInt asks until it gets a whole number. The error is not nil when
// there is no more input.
func readInt(prompt string) (int, error) {
for {
line, err := readLine(prompt)
if err != nil {
return 0, err
}
number, err := strconv.Atoi(line)
if err == nil {
return number, nil
}
fmt.Println("That is not a whole number.")
}
}
func main() {
a, err := readInt("First number: ")
if err != nil {
fmt.Println("No more input.")
return
}
b, err := readInt("Second number: ")
if err != nil {
fmt.Println("No more input.")
return
}
fmt.Printf("Sum: %d\n", a+b)
}Several things from this chapter meet here:
readermoved outsidemain, usingvar, becausereadLineneeds it too. A program should still make only one.readLinedoes the prompt, the read, the trim and the check. It returns the line and an error.readIntcallsreadLine, and asks again until it gets a whole number. Whenerris notnil, it passes the error on to its caller.mainnow says what it wants, and checks for the input running out.
A session, and a run with no input at all:
First number: 3
Second number: abc
That is not a whole number.
Second number: 4
Sum: 7
First number: No more input.
The Part 1 mini-project showed a function that was not part of Go:
guess := readGuess()Now you can write it. It is one line, because a return
can pass on all the values from another call:
func readGuess() (int, error) {
return readInt("Guess: ")
}Because reading can fail, the real call has two values to receive, and it checks the error:
guess, err := readGuess()
if err != nil {
fmt.Println("No more input. The number was", target)
return
}exercises/21/10-read-guess.go is the whole Number
Guessing Game written this way. Unlike the version in Part 4,
abc is no longer counted as a guess.
Open exercises/21/08-read-functions.go. Add a function
readFloat that works like readInt but uses
strconv.ParseFloat, and use it to ask for a price.
Homework
Homework starters are in exercises/21/homework/. Each
one runs as it is and prints a placeholder line. Write your functions
outside main.
Problem 1 — Greet
Open exercises/21/homework/01-greet.go. Write a function
greet that takes a name and prints
Hello, <name>!. Call it with three different
names.
Problem 2 — isEven
Open exercises/21/homework/02-is-even.go. Write
isEven, which takes an int and returns
(not prints) true or false for whether it is
even. Call it on a few values inside fmt.Println so the
bools show.
Problem 3 — Clamp
Open exercises/21/homework/03-clamp.go. Write
clamp(x int, lo int, hi int) int, which returns
lo if x is below lo,
hi if above hi, and x otherwise.
Test it with three calls covering each case.
Challenge — Percent
Open exercises/21/homework/04-percent.go. Write
parsePercent(text string) (int, error). It converts
text to an int and returns that number with a
nil error. If the text is not a number, it returns the
error from strconv.Atoi. If the number is below
0 or above 100, it returns your own error from
errors.New. Write a second function report
that calls it, checks the error at once, and prints the result. Call
report with "85", "abc" and
"150".
Stuck or finished? Open the homework solutions page.