04 - Functions¶
What this session is¶
About an hour. You'll learn how to define your own functions, and why doing so changes everything about how you write programs.
The problem functions solve¶
So far every program you've written has been one block of code inside main(). That works for 5-line programs. It stops working around 30 lines, for three reasons:
- You can't see what the program does by looking at it. The structure is gone.
- You can't reuse anything. If you compute "the square of a number" in three places, you've typed
x * xthree times. - You can't test pieces. The whole program runs or doesn't run; you can't easily check one calculation in isolation.
A function solves all three. It's a named block of code that takes some input and (usually) gives you some output back.
The shape of a function¶
Concrete example:
package main
import "fmt"
func double(x int) int {
return x * 2
}
func main() {
fmt.Println(double(5)) // 10
fmt.Println(double(7)) // 14
}
Type it. Run it. You should see 10 and 14.
Walk through it slowly:
func double(x int) int {- defines a function calleddouble. It takes one input (called a parameter), namedx, of typeint. It returns anint. The body is between the curly braces.return x * 2- computesx * 2and sends that value back to whoever called the function.double(5)- calls the function with5as the value ofx. The function runs, returns10, andPrintlnprints it.
Notice the function double exists outside main. Functions go at the top level of the file, not nested inside main. (Other languages let you nest them; Go does not, in the basic form.)
Multiple parameters¶
A function can take more than one input:
Or, when several parameters share a type, you can collapse the declaration:
Both forms mean the same thing. The second is more idiomatic in Go.
Functions that don't return anything¶
Sometimes a function just does something - it doesn't produce a value to give back. In that case, leave out the return type:
sayHi doesn't return anything. There's no return line in its body. Its job is just to print.
The unusual thing: returning two values at once¶
In most languages, a function returns one thing. Go lets you return two or more things at once. This is uncommon enough to be worth its own section.
func divide(a, b int) (int, int) {
quotient := a / b
remainder := a % b
return quotient, remainder
}
func main() {
q, r := divide(17, 5)
fmt.Println("quotient:", q, "remainder:", r)
// quotient: 3 remainder: 2
}
Type and run.
The new things:
func divide(a, b int) (int, int) {- the return type is(int, int). The parentheses mean "two things, bothints."return quotient, remainder- return both, separated by a comma.q, r := divide(17, 5)- receive both, by writing two names separated by a comma on the left of:=.
This pattern is everywhere in Go. The reason: Go's whole story for handling errors (which you'll learn in a later page) uses it. Every function that can fail returns two things: the result it computed, and an error value telling you whether something went wrong. We'll get there.
For now, just internalize: in Go, "two return values" is normal.
Why functions are worth the trouble¶
Here's a small program that uses two functions:
package main
import "fmt"
func square(x int) int {
return x * x
}
func sumOfSquares(a, b int) int {
return square(a) + square(b)
}
func main() {
fmt.Println(sumOfSquares(3, 4)) // 9 + 16 = 25
}
Look at sumOfSquares. It calls square twice. Functions can call other functions. This is how programs get built up - small named pieces, composed.
Now imagine you write the same logic without functions:
Shorter, sure. But:
- The reader has to figure out what (a * a) + (b * b) means. sumOfSquares(3, 4) says it.
- If you need the squares in three different places, you re-type x * x three times.
- If you later decide squaring should clamp to a maximum (say, return 1000 if the result is bigger), you change one function. In the long version, you change every place.
These benefits compound. They're invisible at 30 lines and decisive at 300.
Variables inside vs outside¶
A variable declared inside a function exists only inside that function. The technical word is scope.
func double(x int) int {
result := x * 2
return result
}
func main() {
fmt.Println(result) // ERROR - `result` doesn't exist out here
}
Each function has its own world. Variables don't leak between them. The way to get information into a function is parameters; the way to get information out is the return value(s).
Exercise¶
In a new file iseven.go:
- Write a function
isEven(n int) boolthat returnstrueifnis even andfalseotherwise.
Hint: use the % operator from Page 02. n % 2 == 0 is a bool.
-
From
main, printisEven(4)andisEven(7). You should seetrueandfalse. -
Write a second function
countEvens(max int) intthat counts how many even numbers are in1, 2, 3, ..., max. It should use aforloop and call yourisEvenfunction for each. -
From
main, printcountEvens(10). You should see5(the evens 2, 4, 6, 8, 10). -
Now print
countEvens(0). What happens? Is that what you expected?
Don't move on until your code works for both countEvens(10) and countEvens(100) (expected: 50).
What you might wonder¶
"Why do I have to declare the return type?"
So Go can catch mistakes for you at compile time. If you say double returns an int and you accidentally write return "hello", Go refuses to build the program and tells you exactly what's wrong. The alternative - types figured out at runtime, like in Python - means some bugs only show up when a real user hits them. Go trades a small amount of typing for a large reduction in runtime surprises.
"What if my function has nothing to return but I forgot to handle a case?"
If you declare a return type but a path through your function doesn't return anything, Go refuses to compile. You'll see "missing return at end of function." This is on purpose - it catches the "I forgot the else branch" bug.
"Can a function call itself?" Yes. That's called recursion and it's a useful tool for certain problems. We'll meet it later when we have something worth recursing through.
"Can I have two functions with the same name?" No, not in the same package. Pick distinct names.
Done¶
You can now: - Define your own functions with parameters and a return type. - Return zero, one, or two values from a function. - Call functions from other functions. - Use functions to give names to operations and compose programs out of small pieces.
You've now learned the core of imperative programming in Go: variables, types, expressions, decisions, loops, and functions. Every Go program - including the ones in real OSS projects you'll eventually contribute to - is built from these primitives. The remaining pages introduce things that make those primitives more powerful: ways to group data (structs), ways to handle many things (slices and maps), ways to handle failure (errors), and so on.
The next page introduces structs - how Go lets you make your own types that group related data together. This is where your programs start to model real-world things.
Next: Making your own types → 05-structs-and-methods.md