Why using functions can improve your code.
How to write a function.
Function signature, body, parameters, results
Return statement
Function call
A function is a block of code invoked by a specific name. It may receive input values. It may also output values.
\n\nLet’s introduce important terms :
\nWhen we create a function, we say that we define it.
The input values are the function parameters.
The output values are the function results.
When we use a function, we say that we call it.
A function usage is called a function call.
Here is an example function taken from the standard library.
\n// TrimSpace returns a slice of the string s, with all leading\n// and trailing white space removed, as defined by Unicode.\nfunc TrimSpace(s string) string {\n // Fast path for ASCII: look for the first ASCII non-space byte\n start := 0\n for ; start < len(s); start++ {\n c := s[start]\n if c >= utf8.RuneSelf {\n // If we run into a non-ASCII byte, fall back to the\n // slower Unicode-aware method on the remaining bytes\n return TrimFunc(s[start:], unicode.IsSpace)\n }\n if asciiSpace[c] == 0 {\n break\n }\n }\n\n // Now look for the first ASCII non-space byte from the end\n stop := len(s)\n for ; stop > start; stop-- {\n c := s[stop-1]\n if c >= utf8.RuneSelf {\n return TrimFunc(s[start:stop], unicode.IsSpace)\n }\n if asciiSpace[c] == 0 {\n break\n }\n }\n\n // At this point s[start:stop] starts and ends with an ASCII\n // non-space bytes, so we're done. Non-ASCII cases have already\n // been handled above.\n return s[start:stop]\n}
\nWe will detail the structure of the function in the next sections.
\n\nWith functions, you can write a block of complex code once and then use it several times in your application.
\n\nIn your go program, you can use functions written by other programmers :
\nFrom the Go standard library
From projects published by Go developers
We will see how to do both in the next sections.
\n\nThis is what we did in the previous blocks of code we wrote when we called the function Println
from the fmt
package. When you call Println
, you call a massive block of code. This block of code is not visible to the caller of the function. It’s hidden. And this is a great thing! We do not need to know the function internals to use it. Functions bring a level of abstraction.
Functions are like black boxes; implementation details are hidden from the caller.
\n\nA function can be seen as a machine; it takes input, performs an action And it outputs something.
\n\nThe number of parameters may vary from 0 to N. Technically speaking, N can be 100 or even 1000.
It’s not a good practice to define a lot of parameters.
\nThe number of results may also vary from 0 to N.
\nA return statement begins with the keyword return
. The return statement is used in a function to :
Terminate the execution of the function
Optionally provide the results of the function
We will see in concrete examples how to use the return statement.
\n\nThe function is composed of :
\nA name (which is an identifier)
A list of parameters enclosed by braces.
Each parameter has a name and a type.
After the parameters list, you find the result list
// functions/declaration-example/main.go\npackage main\n\n// compute the price of a room based on its rate per night\n// and the number of night\nfunc computePrice(rate float32, nights int) float32 {\n return rate * float32(nights)\n}
\nLet’s detail our function :
\nThe name of the function is computePrice
The signature is (rate float32, nights int) float32
The first parameter is named rate
and is of type float32
.
The second parameter is named nights
and is an integer.
There is one result, its type is float32
The function body is return rate * float32(nights)
return
is a keyword: it indicates to Go that what follows is our result.
rate
and float32(nights)
What is the function purpose?
\nThe comment before the function explains what the function does
The name gives us an indication, too; it computes a price.
The signature also gives information
\nIt takes a rate and a number of nights
It returns a float32, the price.
// functions/usage-example/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n johnPrice := computePrice(145.90, 3) //*\\label{funcEx1Us1}\n paulPrice := computePrice(26.32, 10) //*\\label{funcEx1Us2}\n robPrice := computePrice(189.21, 2) //*\\label{funcEx1Us3}\n\n total := johnPrice + paulPrice + robPrice\n fmt.Printf("TOTAL : %0.2f $", total)\n}\n\nfunc computePrice(rate float32, nights int) float32 { //*\\label{funcEx1Dec}\n return rate * float32(nights)\n}
\nThe result of computePrice(145.90, 3)
is stored into the variable johnPrice
.
The result of computePrice(26.32, 10)
is stored into the variable paulPrice
, omputePrice(189.21, 2)
into robPrice
.
We then create a variable total
and assign to it the sum of johnPrice
, paulPrice
and robPrice
.
What is the type of johnPrice
, paulPrice
and total
?
In the function, why do we need to write float32(nights)
, why not just write nights
?
Can we write computePrice(189.21, \"two\")
?
float32
. The function returns a float32; we assign to johnPrice
and paulPrice
the return value of this function. As a consequence, they are float32. Concerning total
, the addition of 3 float32
value is a float32
.
The variable nights
is an integer. We need to convert nights
to float32 to multiply it by a float32
. When we multiply two values, they should have the same type.
The second parameter of the function computePrice is an integer.\"two\"
is a string. You give the function a parameter with the wrong type.
Each function has a scope. A function’s scope begins with the opening curly brace {
and stops with the closing curly brace }
. A scope is a portion of the source code. This is an extent of the source code.
Inside this block, the parameters of the function are defined. We did not need to initialize and assign any variable : rate
and nights
can be used freely :
func computePrice(rate float32, nights int) float32 {\n return rate * float32(nights)\n}
\nThey can be used only in the scope of the function. After the closing curly brace, they do not exist. The parameters only exist in the scope of the function.
\n// functions/scope/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n johnPrice := computePrice(145.90, 3)\n fmt.Println("John:", johnPrice, "rate:", rate)\n}\n\nfunc computePrice(rate float32, nights int) float32 {\n return rate * float32(nights)\n}
\nDoes the previous code compile? Well, it does not, we try to print the variable rate
which do not exists in the scope of the main
function. But rate
exists in the scope of the computePrice
function.
If we try to compile the previous program, we got this error :
\n./main.go:7:43: undefined: rate
\nThe scope is a limitation that allows a perfect encapsulation of the function. From the outside, you cannot manipulate the inside. The scope is like the metallic shield of a machine. The user cannot manipulate the internals of the machine.
\nOf course, you can also define new variables inside the function :
\n// functions/scope2/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n fmt.Println(computePrice(145.90, 3))\n}\n\nfunc computePrice(rate float32, nights int) float32 {\n n := float32(nights)\n return rate * n\n}
\nWe created a new variable n
. Which is initialized with the value float32(nights)
. This variable exists only on the scope of the computePrice
function.
Go gives you the ability to give a name to results. In the previous section, we had an anonymous result. We knew only the type. Here we specify its name and its type.
\nParenthesis that surround the result and the result type are mandatory.
\n\nfunc computePrice(rate float32, nights int) (price float32) {\n price = rate * float32(nights)\n return\n}
\nIn the function body, we assign to price the value rate * float32(nights)
Note that the variable price exists already; we do not need to declare it
The variable price is initialized to the zero value of it’s type (here float32 => 0)
Note also that we simply call return
. We could have written return price
, but it’s not necessary when you name parameters.
When return is called, the function will return the current value of price
Usage syntax is still the same :
\njohnPrice := computePrice(145.90, 3)
\n\n// functions/usage-example-2/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n johnPrice := computePrice(145.90, 3)\n fmt.Printf("TOTAL : %0.2f $", johnPrice)\n}\n\n// compute the price with a 200% margin\nfunc computePrice(rate float32, nights int) (price float32) {\n p := rate * float32(nights)\n p = p * 2\n return\n}
\nDoes this code compile?
What is the output of this program
Yes
TOTAL : 0.00 $
Why? The function computePrice
has a named result price
. In the function body, we never assign to price
the value expected. When return
is called the value of price is returned. The value of price is automatically initialized with the zero value of float32
which is 0.00
. The function returns 0.00
.
Here we have 0 parameters; the function takes no input. But it returns something.
\n\n// functions/usage-example-3/main.go\npackage main\n\nimport (\n "fmt"\n "math/rand"\n "time"\n)\n\nfunc main() {\n\n vacant := vacantRooms()\n fmt.Println("Vacant rooms:", vacant)\n\n}\n\nfunc vacantRooms() int {\n rand.Seed(time.Now().UTC().UnixNano())\n return rand.Intn(100)\n}
\nWe define the functions: vacantRooms
.
This function is called in the main function. The value returned is stored in a variable named vacant.
\n\nNo. The variable is declared but used only once. It is not necessary. We can write directly :
\nfunc main() {\n fmt.Println("Vacant rooms:", vacantRooms())\n}
\nDeclaring a variable makes sense when you need to store the function’s result to use it at another location in your program.
\n\n// functions/usage-example-4/main.go\npackage main\n\nimport (\n "fmt"\n)\n\nfunc main() {\n printHeader()\n}\n\nfunc printHeader() {\n fmt.Println("Hotel Golang")\n fmt.Println("San Francisco, CA")\n}
\nThe function printHeader
will print Hotel Golang
and then on a second line, it will print San Francisco, CA
. In the main function (which also takes 0 parameters and have 0 results) we call printHeader()
.
Can we add a return statement to the function printHeader
?
The following code defines a constant named HotelName
. The constant is used inside the printHeader
function. What do you think of this code?
// functions/quick-question/main.go\npackage main\n\nimport (\n "fmt"\n)\n\nfunc main() {\n const HotelName = "Golang"\n printHeader()\n}\n\nfunc printHeader() {\n fmt.Println("Hotel", HotelName)\n fmt.Println("San Francisco, CA")\n}
\n\nThe function has no result. Adding a return statement to the function is optional. When the program reaches the end of the function, it will terminate it’s execution.
The constant HotelName
is defined in the scope of the main function. This constant does not exist in the scope of the function printHeader
. The program will not compile!
The first function that we used was the main
function. The main
function (from the main
package) has no parameters and no result. This is the entry point of a program.
package main\n\nfunc main() {\n\n}
\n\nWe will continue to develop the management application of the hotel. In the figure 1 you can see the wireframes of the application :
\nTake a look at the following code :
\npackage main\n\nimport (\n "fmt"\n "math/rand"\n "time"\n)\n\nfunc main() {\n const hotelName = "Gopher Paris Inn"\n const totalRooms = 134\n const firstRoomNumber = 110\n\n rand.Seed(time.Now().UTC().UnixNano())\n roomsOccupied := rand.Intn(totalRooms)\n roomsAvailable := totalRooms - roomsOccupied\n\n occupancyRate := (float32(roomsOccupied) / float32(totalRooms)) * 100\n var occupancyLevel string\n if occupancyRate > 70 {\n occupancyLevel = "High"\n } else if occupancyRate > 20 {\n occupancyLevel = "Medium"\n } else {\n occupancyLevel = "Low"\n }\n\n fmt.Println("Hotel:", hotelName)\n fmt.Println("Number of rooms", totalRooms)\n fmt.Println("Rooms available", roomsAvailable)\n fmt.Println(" Occupancy Level:", occupancyLevel)\n fmt.Printf(" Occupancy Rate: %0.2f %%\\n", occupancyRate)\n\n if roomsAvailable > 0 {\n fmt.Println("Rooms:")\n for i := 0; roomsAvailable > i; i++ {\n roomNumber := firstRoomNumber + i\n size := rand.Intn(6) + 1\n nights := rand.Intn(10) + 1\n fmt.Println(roomNumber, ":", size, "people /", nights, " nights ")\n }\n } else {\n fmt.Println("No rooms available for tonight")\n }\n\n}
\nRefactor this code and create functions that will :
\nCompute the occupancy level
Compute the occupancy rate
Print the details of a specific room.
//....\n\n// display information about a room\nfunc printRoomDetails(roomNumber, size, nights int) {\n fmt.Println(roomNumber, ":", size, "people /", nights, " nights ")\n}\n\n// retrieve occupancyLevel from an occupancyRate\n// From 0% to 30% occupancy rate return Low\n// From 30% to 60% occupancy rate return Medium\n// From 60% to 100% occupancy rate return High\nfunc occupancyLevel(occupancyRate float32) string {\n if occupancyRate > 70 {\n return "High"\n } else if occupancyRate > 20 {\n return "Medium"\n } else {\n return "Low"\n }\n}\n\n// compute the hotel occupancy rate\n// return a percentage\n// ex : 14,43 => 14,43%\nfunc occupancyRate(roomsOccupied int, totalRooms int) float32 {\n return (float32(roomsOccupied) / float32(totalRooms)) * 100\n}
\n\npackage main\n\nimport (\n "fmt"\n "math/rand"\n "time"\n)\n\nfunc main() {\n const hotelName = "Gopher Paris Inn"\n const totalRooms = 134\n const firstRoomNumber = 110\n\n rand.Seed(time.Now().UTC().UnixNano())\n roomsOccupied := rand.Intn(totalRooms)\n roomsAvailable := totalRooms - roomsOccupied\n\n occupancyRate := occupancyRate(roomsOccupied, totalRooms)\n occupancyLevel := occupancyLevel(occupancyRate)\n\n fmt.Println("Hotel:", hotelName)\n fmt.Println("Number of rooms", totalRooms)\n fmt.Println("Rooms available", roomsAvailable)\n fmt.Println(" Occupancy Level:", occupancyLevel)\n fmt.Printf(" Occupancy Rate: %0.2f %%\\n", occupancyRate)\n\n if roomsAvailable > 0 {\n fmt.Println("Rooms:")\n for i := 0; roomsAvailable > i; i++ {\n roomNumber := firstRoomNumber + i\n size := rand.Intn(6) + 1\n nights := rand.Intn(10) + 1\n printRoomDetails(roomNumber, size, nights)\n }\n } else {\n fmt.Println("No rooms available for tonight")\n }\n\n}\n\n// ... 3 functions definition (cf. supra)
\n\nLet’s detail the construction of each function :
\n\n// display information about a room\nfunc printRoomDetails(roomNumber, size, nights int) {\n fmt.Println(roomNumber, ":", size, "people /", nights, " nights ")\n}
\nThis function takes three parameters: roomNumber
, size
and nights
. Those three parameters are of type int
. The function returns nothing.
When the parameters have the same, we can write :
\nfunc printRoomDetails(roomNumber, size, nights int)
\ninstead of :
\nfunc printRoomDetails(roomNumber int, size int, nights int)
\nThis last notation is perfectly legal, but it requires more characters and more understanding effort from the reader.
\nThe body of the function is a call to fmt.Println
. We print :
the value of roomNumber
the string \":\"
the value of size
the string \"people /\"
the value of nights
the string \" nights \"
When the value of roomNumber
is 169, the value of size
is four, and the value of nights
is 5: the function will print :
169 : 4 people / 5 nights
\nWhen the value of nights is equal to 1 (and the other variables have the same value) the output result is :
\n169 : 4 people / 1 nights
\nIt’s a bug! We could have designed our program to output “night” without the “s” if the number of nights is equal to 1.
\n\nWrite a second version of the function printRoomDetails
that corrects the plural singular bug.
The input parameter types are questionable. Why?
// retrieve occupancyLevel from an occupancyRate\n// From 0% to 30% occupancy rate return Low\n// From 30% to 60% occupancy rate return Medium\n// From 60% to 100% occupancy rate return High\nfunc occupancyLevel(occupancyRate float32) string {\n if occupancyRate > 70 {\n return "High"\n } else if occupancyRate > 20 {\n return "Medium"\n } else {\n return "Low"\n }\n}
\nHere the function takes in input a float32
parameter. The name of this parameter is occupancyRate
. The function returns a string
.
We have an if / else if / else construct. It is adapted in this case where we have three possible outcomes.
\n\nWrite a second version of the function occupancyLevel
which use a switch/case instead of an if / else if / elsestructure.
// compute the hotel occupancy rate\n// return a percentage\n// ex : 14,43 => 14,43%\nfunc occupancyRate(roomsOccupied int, totalRooms int) float32 {\n return (float32(roomsOccupied) / float32(totalRooms)) * 100\n}
\nWe take two integers as input. Because the two parameters have the same type, we could have written :
\nfunc occupancyRate(roomsOccupied, totalRooms int) float32
\nThe function body is composed of a return statement. The result of the computation (float32(roomsOccupied) / float32(totalRooms)) * 100
is returned.
float32(X)
will convert the variable X
to a float32. We can convert only numeric types to float32 :
// try to convert a string to a float32!\nfloat32("test")
\nIt Will not compile! (the error message is cannot convert test (type untyped string) to type float32)
\n\nTo detect if a quantity is singular, you have to compare it with 1. If the quantity is 1, then your word will be singular.
\n\n// functions/plural-singular-sol-1/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n printRoomDetails(112, 2, 2)\n}\n\n// display information about a room\nfunc printRoomDetails(roomNumber, size, nights int) {\n nightText := "nights"\n if nights == 1 {\n nightText = "night"\n }\n fmt.Println(roomNumber, ":", size, "people /", nights, nightText)\n}
\nHere we define a variable nightText
. We initialize it with the value \"nights\"
. Then we add an if. If nights is equal to 1, then the value of the variable nightText
is overwritten to \"night\"
Then the variable nightText
is used as a parameter of fmt.Println
.
// functions/plural-singular-sol-2/main.go\npackage main\n\nimport "fmt"\n\nfunc main() {\n printRoomDetails2(112, 3, 4)\n}\n\n// display information about a room\n// This alternative works, but the code is duplicated...\nfunc printRoomDetails2(roomNumber, size, nights int) {\n if nights == 1 {\n fmt.Println(roomNumber, ":", size, "people /", nights, "night")\n } else {\n // practically the same line as before!\n // avoid this\n fmt.Println(roomNumber, ":", size, "people /", nights, "nights")\n }\n}
\nHere we do not define any variable. We have instead an if / else construct. This code will work. But it’s not optimal :
\nThe if construct is easier to read than the if/else construct. In the if-else construct, the reader of your code will need to think about the opposite of being equal to 1.
We have code duplication. The call to fmt.Println is repeated, and the two calls are practically the same.
What if nights
is equal to 0
?
We could replace the int with an unsigned integer. roomNumber, size, nights are always positive... If we keep the int type, we could have a negative number of nights, which is impossible and may cause some serious bugs in the system. We could generate negative prices : -3\\text{ nights}\\times30\\$=-90\\$ ! We will give money to the customer to stay in our hotel!
\n\nWhen you have an if / else if / else, you can replace it with a switch case.
\nfunc occupancyLevel(occupancyRate float32) string {\n switch {\n case occupancyRate > 70:\n return "High"\n case occupancyRate > 20:\n return "Medium"\n default:\n return "Low"\n\n }\n}
\nHere no expression is provided in the switch case header. This is the equivalent of :
\nswitch true{\n case occupancyRate > 70:\n //..\n }
\nThe program will :
\nEvaluate the boolean expression occupancyRate > 70
. If it’s equal to true
, then it will return the string \"High\"
. If not, the next step is executed.
Evaluate the boolean expression occupancyRate > 20
. If it’s equal to true
, then it will return the string \"Medium\"
.
When the two previous expressions are false, the default block is executed, which returns the string \"Low\"
Keep in mind that the return statement will terminate the function execution.
\n\nFunctions names should express clearly the operation that will be executed by the function
\nAvoid names with one letter
Use camel case
No more than two words
\nsendEmail
is good, sendEmailToTheOperationalTeam
is not so goodParameters and results names should also convey information. Follow the same advice given before.
Avoid mentioning the type in the identifier.
\ndescriptionString
is bad, description
is better.Choose the parameter types and the return types carefully. Well-chosen types improve the readability of your code.
\nnights
is an unsigned integer (uint
), not an int
. A negative number of nights does not exist (at least in our human dimension)A function should have at least one parameter. True or False?
In a function, what is the purpose of a return statement?
What is the difference between the result(s) list and the parameter(s) list ?
Can we write Go programs without functions?
Fill the blank: To launch the execution of a function, I need to ______ it.
When I define a function, it will be launched at the beginning of my program. True or False?
A function should have at least one parameter. True or False?
\nFalse
A function can have 0 parameter.
In a function, what is the purpose of a return statement?
\nA return statement stops the execution of the function.
It also optionally provide one or more result values
What is the difference between the result(s) list and the parameter(s) list ?
\nThey are both lists of identifiers and types
The result(s) list is the output of the function, what the function will return when it’s called
The parameter(s) list is the input of the function. The inner logic of the function will use input parameters
Can we write Go programs without functions?
\nYou can, but you will need at least a main function to start your program.
Functions are a great tool to build programs; you may see them as machines that can be called everywhere.
Fill the blank: To launch a function, I need to ____ it.
\nWhen I define a function, it will be launched at the beginning of my program. True or False?
\nThis is false
The act of defining a function is different from calling a function.
A function will be executed when it’s called.
Functions are convenient building blocks of programs
A function is like a “machine” :
\nDesigning / Building the machine => define the function
Start the machine => call the function
Give your functions meaningful and short names
Functions may have
\nParameters: input
Results: output
The return statement terminates the execution of the function and optionally provide results to the caller1.
See https://golang.org/ref/spec#Return_statements↩︎
Previous
\n\t\t\t\t\t\t\t\t\tControl Statements
\n\t\t\t\t\t\t\t\tNext
\n\t\t\t\t\t\t\t\t\tPackages and imports
\n\t\t\t\t\t\t\t\t