Go: from zero to your own blog Lesson 5 of 50
Functions in Go: parameters, return and two values at once
The fifth lesson of the Go course. How to declare a function, and what a parameter, an argument and return actually are. The heart of the lesson: a Go function returns two values at once — the result, and whether it worked. That is why the language has no exceptions. We read the result, ok := idiom and the underscore.
Why this matters
Programs grow, and the same steps start repeating. A function is a written-down order of actions with a name on it: write it once, call it as often as you like.
But the heart of this lesson is something else. In Go a function can return two things at once: the result, and whether it worked. All error handling in the language rests on that: Go has no familiar try/catch, and an error is an ordinary returned value. There is a mechanism for genuinely exceptional cases (panic), but it is not for this.
The whole thing at once
A new folder, go mod init sabaq04, main.go:
package main
import "fmt"
func greet(name string) string {
return "Salem, " + name + "!"
}
func divide(a, b float64) (float64, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
func main() {
fmt.Println(greet("alem"))
result, ok := divide(10, 4)
fmt.Println("10 / 4 =", result, "· worked:", ok)
result, ok = divide(10, 0)
fmt.Println("10 / 0 =", result, "· worked:", ok)
}
Before you run it, and without looking below, say out loud what it will print. Then run it and compare: the difference is exactly where you do not know the language yet.
go run . prints:
Salem, alem!
10 / 4 = 2.5 · worked: true
10 / 0 = 0 · worked: false
Look at the last line. To leave no false impression: Go would not have fallen over without our check either. Dividing a float64 by zero is not a failure in Go — it yields +Inf, -Inf or NaN. The check is not there to save the program but to stop a meaningless number travelling on: +Inf would reach the page quite calmly and be shown to a reader.
Taking it apart
How a declaration reads
func greet(name string) string {
Left to right: func — a function begins; greet — its name; (name string) — what you must bring and what it will be called inside; the string after the brackets — what comes out.
Picture it. An order form at a workshop. A line at the top for what you bring in, a line at the bottom for what you collect. Until the form is filled in and handed over, the workshop does nothing.
Parameter and argument
Two different words, and they are constantly mixed up.
A parameter is name in the declaration: an empty place kept for a future value.
An argument is "alem" in the call greet("alem"): the thing you put in that place.
Picture it. A line on a form, and what you wrote on it. The line is always the same; what goes on it is different every time.
return
return ends the function’s work and hands the result outward. Anything written after it does not run.
Picture it. Handing the finished job through the collection window and closing the ticket. Once it is handed over, nobody goes back to that ticket, even if there is more written below it in the book.
Two values instead of exceptions
Here is the heart of the lesson:
func divide(a, b float64) (float64, bool) {
The brackets around two types mean the function hands back two things at once. The first is the result of the division, the second is whether it worked.
Why do it that way. Dividing by zero is not the program breaking, it is an ordinary case you have to allow for. Other languages tend to “throw” such things through a separate mechanism, which is easy not to catch. Go does not tuck the failure away to the side; it puts it straight into your hands as a second value.
Picture it. A certificate handed out at a counter. You get not just the paper but a mark on it: issued, or refused. One is never given without the other — the mark arrives in your hand with the paper, and to miss it you have to look away on purpose. We will see what that looks like in code below.
Taking two values
Two names, separated by a comma, on the left of :=:
result, ok := divide(10, 4)
The order matters: the result first, the flag second. You choose the names and the compiler does not check them: write ok, result := divide(10, 4) and it still builds — ok just holds a number and result a yes-or-no. The order carries the meaning, not the name.
The second name is nearly always called ok, and once real errors turn up, err. That is not a rule of the language but a shared habit, and it is what makes other people’s code readable at a glance.
When you do not want the second value
You cannot simply leave it out: Go insists you take everything a function returned. The mark for “I do not need this” is an underscore:
result, _ := divide(10, 4)
Picture it. A line on a form you strike through. Leaving it blank will not do — the form is rejected. Striking it through will: it shows you saw the line and passed it over on purpose.
Doing that to ok is almost never right: you are throwing away by hand the one notice that something went wrong.
Several parameters of the same type
func divide(a, b float64) (float64, bool)
a and b are both float64, and the type is written once, at the end. When the types differ, each gets its own: func post(title string, views int).
A function that returns a function
In Go a function is a value like a number or a string. It can go into a variable, be passed as a parameter, and be returned from another function. The last one looks unfamiliar, but you will meet it in this very course, so let us take it now:
func counter() func() int {
n := 0
return func() int {
n = n + 1
return n
}
}
func main() {
next := counter()
fmt.Println(next())
fmt.Println(next())
fmt.Println(next())
other := counter()
fmt.Println("another counter:", other())
}
1
2
3
another counter: 1
The declaration reads the way it did before: func counter() is a function with no parameters, and its return type is func() int — “a function with no parameters that returns an integer”.
Now the point. n is declared inside counter, and by every rule so far it should have disappeared when counter finished. But it is alive: the returned function remembers it and changes it on every call. Such a function is called a closure — it closed over a variable from the function it was born in.
The last line of the output is the proof that this is not one shared variable: the second counter has an n of its own and starts at one.
Picture it. A note in a pocket. The function has left the house it was born in, but it took the slip of paper with the number along — and it corrects its own slip, not somebody else’s.
Do not be put off by the notation; you will see it often. In the lesson on wrappers you will write a function that takes a handler and returns a new one — it is built exactly like this, and what stands in for n there is whatever the wrapper has to remember.
The lesson map
Say it in your own words
Answer out loud or on paper without looking. The answers are at the end of the lesson.
- How does a parameter differ from an argument?
- Why does
dividereturn aboolinstead of just stopping the program on a division by zero? - What happens if you write
result := divide(10, 4), with one name on the left?
Exercise
Required. Write a function readingTime(words int) int that works out an article’s reading time at 200 words a minute, rounded up: (words + 199) / 200. Integer division throws the remainder away, so the 199 is added before dividing: a part-minute still counts, while exactly 400 words come out as 2 rather than 3. Call the function from main for 150, 400 and 1000 words and check the answers: they should be 1, 2 and 5. Zero words take zero minutes, which is right.
Optional.
- Make
readingTimereturn two values: the minutes, and aboolfor whether the word count is sensible. Checking that needs anif, which is the next lesson: if it will not come yet, return to this after it. - Replace
okwith_in thedivide(10, 0)call and see what the program prints. That is exactly how errors get lost. - Write a function with a result type in its declaration and no
return. Read what Go says.
Where this goes in your blog
Many functions in your blog will return two values: an article and “was it found”, text and “did it parse”, a database connection and “did it connect”. In the lesson on errors the second value becomes an error rather than a bool, and that is Go’s real working idiom.
Answers
Show the answers
- A parameter is the name in the declaration, an empty place for a value. An argument is the actual value you supplied at the call. The parameter is always the same; the arguments differ every time.
- Because a division by zero here is not a disaster: Go would return
+Infand carry on. That is exactly the danger — a meaningless number spreading silently through the program. By returning a flag, the function leaves the decision to whoever called it instead of pretending an answer was obtained. - The program will not build. Go says something like
assignment mismatch: 1 variable but divide returns 2 values: the language will not let a returned value be lost in silence, and the underscore is there for turning one down.
Sources
If you have found a mistake or a typo in this article, tell us about it
Comments (0)
Log in to leave a comment →
No comments yet. Be the first.