Defining And Using Functions In Go: Creating Reusable Functions, Including Variadic Functions And Closures
Overview
This chapter explores the fundamentals of functions in Go, guiding you through creating reusable functions with multiple arguments and return values. You will learn how to define variadic functions that can handle a dynamic number of arguments, utilize closures to capture external variables, and understand methods for attaching functions to types. Additionally, the chapter delves into advanced concepts like deferred function calls, error handling, function types, and the use of init functions to perform pre-execution setup. Through practical examples, you’ll build the skills needed to create flexible, efficient Go functions.
Arguments
Functions can take N number of arguments, including other functions.
package main
import "fmt"
func main() {
sayHello("Hello")
}
func sayHello(greeting string) {
fmt.Println(greeting)
}package main
import "fmt"
func main() {
sayHello("Hello")
}
func sayHello(greeting string) {
fmt.Println(greeting)
}Because Go is a typed language you must defined the types of the arguments in the function definition.
The type of the argument follows the name of the argument.
Arguments Of Same Type
Identical typed arguments can be declared once.
package main
import "fmt"
func main() {
sayHello("Hello", "Mark")
sayHello2("Hello", "Mark")
}
func sayHello(greeting, name string) {
fmt.Printf("%s, %s", greeting, name)
}
func sayHello2(greeting string, name string) {
fmt.Printf("%s, %s", greeting, name)
}package main
import "fmt"
func main() {
sayHello("Hello", "Mark")
sayHello2("Hello", "Mark")
}
func sayHello(greeting, name string) {
fmt.Printf("%s, %s", greeting, name)
}
func sayHello2(greeting string, name string) {
fmt.Printf("%s, %s", greeting, name)
}Both examples are functionally identical. Although for clarity the more explicit (longer) version is preferred.
Return Arguments
Functions can return 0 to N numbers of values, though it is considered best practice to not return more than two or three.
When returning an error it is considered best practice to return that value as the last value.
func one() string
func two() (string, error)
func three() (int, string, error)
Declaring Return Arguments
Multiple Return Arguments
Multiple return arguments need to be placed inside of ().
package main
import "fmt"
func main() {
l, c := slicer([]string{"John", "Paul", "George", "Ringo"})
fmt.Println(l)
fmt.Println(c)
}
func slicer(s []string) (int, int) {
return len(s), cap(s)
}Functions Accepting Arguments From Functions
You can accept the return arguments of a function into a function, as long as the return types (and number of return arguments) lines up with the number of arguments expected to the calling function.
package main
import (
"fmt"
)
func main() {
takeTwo(returnTwo())
}
func returnTwo() (string, string) {
return "hello", "world"
}
func takeTwo(a, b string) {
fmt.Println(a, b)
}Named Returns
The return of a go function can be given a name and used just like a variable declaration.
- Initialized with zero value in function scope
- Executing a
returnwith no value will return the value of the named return. - Names are not mandatory
func exists() (exist bool) {
exist = true
return
}
It is best practice to not use named returns, unless they are needed for documentation.
Given the following function that returns latitude and longitude, it would be hard to remember which parameter is meant for which value with this given signature:
func coordinates() (int, int)
Using named returns should only be used for documentation purposes. Even then, it could be argued to never use it.
func coordinates() (lat, lng int)
When Named Returns Go Bad
Here is an example of what can happen with named returns.
Please don’t do this in your code.
func someFunction(val int) (ok bool, err error) {
if val == 0 {
return false, nil
}
if val < 0 {
return false, fmt.Errorf("value can't be negative %d", val)
}
ok = true
return
}
package main
import (
"fmt"
)
func main() {
b, err := someFunction(1)
fmt.Println("### b ->", b)
fmt.Println("### err ->", err)
b, err = someFunction(-1)
fmt.Println("### b ->", b)
fmt.Println("### err ->", err)
}
// section: named
func someFunction(val int) (ok bool, err error) {
if val == 0 {
return false, nil
}
if val < 0 {
return false, fmt.Errorf("value can't be negative %d", val)
}
ok = true
return
}
// section: namedShadowing
Go has the ability to shadow a variable. As such, care must be taken to ensure you are accessing the proper variable instance.
func main() {
i := 0
for n := 0; n < 5; n++ {
i := n // redeclare i (shadow)
i++
fmt.Println("loop:", i)
}
fmt.Println("final:", i)
}
package main
import "fmt"
// section: main
func main() {
i := 0
for n := 0; n < 5; n++ {
i := n // redeclare i (shadow)
i++
fmt.Println("loop:", i)
}
fmt.Println("final:", i)
}
// section: main
/*
// section: output
loop: 1
loop: 2
loop: 3
loop: 4
loop: 5
final: 0
// section: output
*/Output:
Shadowing Errors
It’s very common to shadow errors as a result of calling functions and being inside another scope, such as a loop:
func main() {
err := boom()
fmt.Println(err)
for i := 5; i >= -1; i-- {
n, err := inc(i) // err get's shadowed here
if err != nil {
fmt.Println(err)
continue
}
fmt.Println(n)
}
fmt.Println(err)
}
package main
import (
"errors"
"fmt"
)
// section: main
func main() {
err := boom()
fmt.Println(err)
for i := 5; i >= -1; i-- {
n, err := inc(i) // err get's shadowed here
if err != nil {
fmt.Println(err)
continue
}
fmt.Println(n)
}
fmt.Println(err)
}
// section: main
func boom() error {
return errors.New("boom")
}
func inc(i int) (int, error) {
if i < 0 {
return 0, fmt.Errorf("must be a positive number: %d", i)
}
i++
return i, nil
}
/*
// section: output
boom
6
5
4
3
2
1
must be a positive number: -1
boom
// section: output
*/Output:
package main
import (
"errors"
"fmt"
)
// section: main
func main() {
err := boom()
fmt.Println(err)
for i := 5; i >= -1; i-- {
n, err := inc(i) // err get's shadowed here
if err != nil {
fmt.Println(err)
continue
}
fmt.Println(n)
}
fmt.Println(err)
}
// section: main
func boom() error {
return errors.New("boom")
}
func inc(i int) (int, error) {
if i < 0 {
return 0, fmt.Errorf("must be a positive number: %d", i)
}
i++
return i, nil
}
/*
// section: output
boom
6
5
4
3
2
1
must be a positive number: -1
boom
// section: output
*/While shadowing isn’t usually an issue, it’s something to be aware of to ensure you don’t use the wrong scope of a variable.
Detecting Shadows
The shadow tool can detect variable shadowing in your code. You can install it with:
go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
This will install the shadow command in your $GOPATH/bin directory, and can now be run in any project with this syntax:
shadow ./...
Running it on the previous code returns this result:
main.go:13:6: declaration of "err" shadows declaration at line 10
Alternatively, you can run the shadow analyzer via go vet:
go vet -vettool=$(which shadow) ./...
For comprehensive static analysis including shadow detection, consider using golangci-lint which is covered in the tooling module.
Function Exercise (10 Mins)
package main
import (
"fmt"
"strings"
)
func main() {
// Say "Good morning" to John
echo(sayHello("John"))
fmt.Println(Duplicate("gopher"))
fmt.Println(ProperName("rob", "pike"))
}
// Create a function called `echo` that takes a single argument
// of type string and prints it out.
// func() {}
// Change sayHello to allow the greeting to be customized
func sayHello(name string) string {
return "Hello " + name
}
// Change the following function to remove the named return
func Duplicate(s string) (dup string) {
dup = s + " " + s
return
}
// Change the following function to declare the argument type for each argument provided
func ProperName(first, last string) string {
return fmt.Sprintf("%s %s", strings.Title(first), strings.Title(last))
}
// Fix the following function declaration
func Broken(s string) string, error {
return s, errors.New("I'm broken")
}package main
import (
"fmt"
"strings"
)
func main() {
// Say "Good morning" to John
echo(sayHello("John"))
fmt.Println(Duplicate("gopher"))
fmt.Println(ProperName("rob", "pike"))
}
// Create a function called `echo` that takes a single argument
// of type string and prints it out.
// func() {}
// Change sayHello to allow the greeting to be customized
func sayHello(name string) string {
return "Hello " + name
}
// Change the following function to remove the named return
func Duplicate(s string) (dup string) {
dup = s + " " + s
return
}
// Change the following function to declare the argument type for each argument provided
func ProperName(first, last string) string {
return fmt.Sprintf("%s %s", strings.Title(first), strings.Title(last))
}
// Fix the following function declaration
func Broken(s string) string, error {
return s, errors.New("I'm broken")
}Function Solution
package main
import (
"errors"
"fmt"
"strings"
)
func main() {
// Say "Good morning" to John
echo(sayHello("Good morning", "John"))
fmt.Println(Duplicate("gopher"))
fmt.Println(ProperName("rob", "pike"))
}
// Create a function called `echo` that takes a single argument
// of type string and prints it out.
func echo(s string) {
fmt.Println(s)
}
// Change sayHello to allow the greeting to be customized
func sayHello(greeting string, name string) string {
return greeting + " " + name
}
// Change the following function to remove the named return
func Duplicate(s string) string {
return s + " " + s
}
// Change the following function to declare the argument type for each argument provided
func ProperName(first string, last string) string {
return fmt.Sprintf("%s %s", strings.Title(first), strings.Title(last))
}
// Fix the following function declaration
func Broken(s string) (string, error) {
return s, errors.New("I'm broken")
}package main
import (
"errors"
"fmt"
"strings"
)
func main() {
// Say "Good morning" to John
echo(sayHello("Good morning", "John"))
fmt.Println(Duplicate("gopher"))
fmt.Println(ProperName("rob", "pike"))
}
// Create a function called `echo` that takes a single argument
// of type string and prints it out.
func echo(s string) {
fmt.Println(s)
}
// Change sayHello to allow the greeting to be customized
func sayHello(greeting string, name string) string {
return greeting + " " + name
}
// Change the following function to remove the named return
func Duplicate(s string) string {
return s + " " + s
}
// Change the following function to declare the argument type for each argument provided
func ProperName(first string, last string) string {
return fmt.Sprintf("%s %s", strings.Title(first), strings.Title(last))
}
// Fix the following function declaration
func Broken(s string) (string, error) {
return s, errors.New("I'm broken")
}Variadic Arguments
Functions can take variadic arguments, meaning 0 to N arguments of the same type. This is done by prepending the type of the argument with ....
package main
import "fmt"
func main() {
sayHello("John", "Paul", "George", "Ringo")
// Hello John
// Hello Paul
// Hello George
// Hello Ringo
sayHello("George")
// George
sayHello()
//
}
func sayHello(names ...string) {
for _, n := range names {
fmt.Printf("Hello %s\n", n)
}
}Variadic Argument Position
Variadic arguments must be the last argument of the function.
package main
import "fmt"
func main() {
sayHello("John", "Paul", "George", "Ringo")
}
func sayHello(names ...string, group string) {
for _, n := range names {
fmt.Printf("Hello %s\n", n)
}
}package main
import "fmt"
func main() {
sayHello("John", "Paul", "George", "Ringo")
}
func sayHello(names ...string, group string) {
for _, n := range names {
fmt.Printf("Hello %s\n", n)
}
}Error
variadic-bad.go:9: can only use ... with final parameter in list
Exploding Variadic Arguments
Slices can be “exploded” using trailing ... to send them as variadic arguments to a function.
package main
import "fmt"
func main() {
beatles := []string{"John", "Paul", "George", "Ringo"}
sayHello(beatles...)
// Hello John
// Hello Paul
// Hello George
// Hello Ringo
}
func sayHello(names ...string) {
for _, n := range names {
fmt.Printf("Hello %s\n", n)
}
}When To Use Variadic
While using a variadic argument is not the common use case, it certainly can
make your code look nicer and easier to read and use. The most common reason
to use a variadic is when your function is commonly called with zero, one, or many
arguments.
Take the following code that looks up Users by ID.
func LookupUsers(ids []int) []Users {
for i, id := range ids {
...
}
}
You may have one or many users
to look up. Without using a variadic signature, your code would look like this:
var id1, id2, id3 int
id1 = 1
id2 = 2
id3 = 3
var ids := []int{1, 2, 3)
LookupUsers([]int{id1})
LookupUsers([]int{id1, id2, id3})
LookupUsers(ids)
By changing the signature to be variadic as follows:
func LookupUsers(ids ...int) []Users {
for i, id := range ids {
...
}
}
The code will now look like this, but function the same way as before:
var id1, id2, id3 int
id1 = 1
id2 = 2
id3 = 3
var ids := []int{1, 2, 3)
LookupUsers(id1)
LookupUsers(id1, id2, id3)
LookupUsers(ids...)
Variadic Exercise (10 Mins)
package main
import "fmt"
// Create a function called `printSlice` that takes a slice of strings
// and prints them out.
// func () {}
// Create a function called `printAll` that takes a string as a variadic
// argument and prints them out.
// func () {}
func main() {
fruit := []string{"banana", "orange", "strawberry", "apple"}
fmt.Println("Print out a slice")
printSlice(fruit)
fmt.Println()
fmt.Println("Print out multiple items")
printAll("banana", "orange", "strawberry", "apple")
// Explode the arguments to pass a slice to printAll
fmt.Println() // insert blank line in output
printAll(fruit...)
// You can also call printAll with no arguments
fmt.Println() // insert blank line in output
printAll()
}package main
import "fmt"
// Create a function called `printSlice` that takes a slice of strings
// and prints them out.
// func () {}
// Create a function called `printAll` that takes a string as a variadic
// argument and prints them out.
// func () {}
func main() {
fruit := []string{"banana", "orange", "strawberry", "apple"}
fmt.Println("Print out a slice")
printSlice(fruit)
fmt.Println()
fmt.Println("Print out multiple items")
printAll("banana", "orange", "strawberry", "apple")
// Explode the arguments to pass a slice to printAll
fmt.Println() // insert blank line in output
printAll(fruit...)
// You can also call printAll with no arguments
fmt.Println() // insert blank line in output
printAll()
}Variadic Solution
package main
import "fmt"
// Create a function called `printSlice` that takes a slice of strings and prints them out.
func printSlice(sl []string) {
for _, s := range sl {
fmt.Println(s)
}
// or printAll(sl...)
}
// Create a function called `printAll` that takes a string as a variadic argument and prints them out.
func printAll(sl ...string) {
for _, s := range sl {
fmt.Println(s)
}
}
func main() {
fruit := []string{"banana", "orange", "strawberry", "apple"}
fmt.Println("Print out a slice")
printSlice(fruit)
fmt.Println()
fmt.Println("Print out multiple items")
printAll("banana", "orange", "strawberry", "apple")
// Explode the arguments to pass a slice to printAll
fmt.Println() // insert blank line in output
printAll(fruit...)
// You can also call printAll with no arguments
fmt.Println() // insert blank line in output
printAll()
}package main
import "fmt"
// Create a function called `printSlice` that takes a slice of strings and prints them out.
func printSlice(sl []string) {
for _, s := range sl {
fmt.Println(s)
}
// or printAll(sl...)
}
// Create a function called `printAll` that takes a string as a variadic argument and prints them out.
func printAll(sl ...string) {
for _, s := range sl {
fmt.Println(s)
}
}
func main() {
fruit := []string{"banana", "orange", "strawberry", "apple"}
fmt.Println("Print out a slice")
printSlice(fruit)
fmt.Println()
fmt.Println("Print out multiple items")
printAll("banana", "orange", "strawberry", "apple")
// Explode the arguments to pass a slice to printAll
fmt.Println() // insert blank line in output
printAll(fruit...)
// You can also call printAll with no arguments
fmt.Println() // insert blank line in output
printAll()
}Methods
Methods are syntactic sugar for declaring functions on types.
package main
import "fmt"
type Beatle struct {
Name string
Instrument string
}
func (b Beatle) String() string {
return fmt.Sprintf("%s plays %s", b.Name, b.Instrument)
}
func main() {
b := Beatle{Name: "Ringo", Instrument: "Drums"}
fmt.Println(b.String()) // Ringo plays Drums
}Method Receivers
Methods also allow us to namespace a function to a receiver.
func (b Beatle) String() string {
return fmt.Sprintf("%s plays %s", b.Name, b.Instrument)
}
func String(b Beatle) string {
return fmt.Sprintf("%s plays %s", b.Name, b.Instrument)
}
func main() {
b := Beatle{Name: "Ringo", Instrument: "Drums"}
fmt.Println(b.String()) // Ringo plays Drums
fmt.Println(String(b)) // Ringo plays Drums
}
package main
import "fmt"
type Beatle struct {
Name string
Instrument string
}
// section: code
func (b Beatle) String() string {
return fmt.Sprintf("%s plays %s", b.Name, b.Instrument)
}
func String(b Beatle) string {
return fmt.Sprintf("%s plays %s", b.Name, b.Instrument)
}
func main() {
b := Beatle{Name: "Ringo", Instrument: "Drums"}
fmt.Println(b.String()) // Ringo plays Drums
fmt.Println(String(b)) // Ringo plays Drums
}
// section: codeBoth functions are equivalent, but the former namespaces the String function to the Beatle type, allowing us to have multiple String functions with different receivers.
Value Vs. Pointer Receivers
You can define a receiver for a method as either a value or a pointer
receiver. They will behave differently.
A value receiver can not mutate the data, while a pointer receiver can.
package main
import (
"fmt"
"strings"
)
type User struct {
First string
Last string
}
// Pointer Receiver
func (u *User) Titleize() {
u.First = strings.Title(u.First)
u.Last = strings.Title(u.Last)
}
// Value Receiver
func (u User) Reset() {
u.First = ""
u.Last = ""
}
func main() {
u := User{First: "cory", Last: "lanou"}
u.Titleize()
fmt.Println(u)
// Reset can't mutate the data as it's defined with a value receiver
u.Reset()
fmt.Println(u)
}package main
import (
"fmt"
"strings"
)
type User struct {
First string
Last string
}
// Pointer Receiver
func (u *User) Titleize() {
u.First = strings.Title(u.First)
u.Last = strings.Title(u.Last)
}
// Value Receiver
func (u User) Reset() {
u.First = ""
u.Last = ""
}
func main() {
u := User{First: "cory", Last: "lanou"}
u.Titleize()
fmt.Println(u)
// Reset can't mutate the data as it's defined with a value receiver
u.Reset()
fmt.Println(u)
}Nil Receivers
Care must be taken when using pointer receivers. If the receiver itself is nil, you can still call methods that don’t reference the receiver, but if you call a method that does reference a receiver, it will panic with a nil pointer dereference.
In the code below, there is a IAmGroot method, and as it doesn’t reference the pointer receiver, we can still call the method even though the variable u is nil.
However, when we try to call Reset, this will panic, as it tries to reference the receiver, which is nil.
package main
import "fmt"
type User struct {
First string
Last string
}
func (u *User) Reset() {
u.First = ""
u.Last = ""
}
func (u *User) IAmGroot() string {
return "I am Groot"
}
func main() {
var u *User
fmt.Println(u.IAmGroot())
u.Reset()
}Nil Receivers Checks
You can check to see if the receiver is nil before referencing it. While this may work in some scenarios, it may inadvertently end up as a Nop in your code and therefore fail silently, creating a very difficult bug to find later on.
You must decide is it better for the code to panic, or better for the code to fail silently. In most cases, you would prefer a panic and make sure you have test coverage to ensure this doesn’t happen at runtime.
package main
import "fmt"
type User struct {
First string
Last string
}
func (u *User) Reset() {
if u == nil {
return
}
u.First = ""
u.Last = ""
}
func (u *User) IAmGroot() string {
return "I am Groot"
}
func main() {
var u *User
fmt.Println(u.IAmGroot())
u.Reset()
}Resetting With Pointer Receivers
We saw earlier that we defined a reset method by setting each field one by one to a default value. You can also use a pointer receiver to set the entire instance back to another value:
package main
import "fmt"
type User struct {
First string
Last string
}
func (u *User) Reset() {
*u = User{}
}
func main() {
u := User{First: "Rob", Last: "Pike"}
fmt.Println(u)
u.Reset()
fmt.Println(u)
}package main
import "fmt"
type User struct {
First string
Last string
}
func (u *User) Reset() {
*u = User{}
}
func main() {
u := User{First: "Rob", Last: "Pike"}
fmt.Println(u)
u.Reset()
fmt.Println(u)
}Output:
{Rob Pike}
{ }{Rob Pike}
{ }Methods Expressions
Methods are really just syntactic sugar for functions that are name-spaced to a concrete type. While we typically don’t use them in the fashion shown below, this may help understand that the receiver is in fact just the first argument in the function call.
package main
import (
"fmt"
)
type User struct {
First string
Last string
}
func (u User) Greet(greeting string) {
fmt.Println(greeting + " " + u.First + " " + u.Last)
}
func main() {
u := User{"Cory", "LaNou"}
u.Greet("hi")
User.Greet(u, "goodbye")
}package main
import (
"fmt"
)
type User struct {
First string
Last string
}
func (u User) Greet(greeting string) {
fmt.Println(greeting + " " + u.First + " " + u.Last)
}
func main() {
u := User{"Cory", "LaNou"}
u.Greet("hi")
User.Greet(u, "goodbye")
}Method Chaining
In many languages, method chaining is very common. While it’s not as common in the standard library for Go, there are many third party packages that take advantage of method chaining.
The key to method chaining is to return a pointer to the value of the current instance. Keep in mind that this really only works if you don’t have to return any other values from the method.
The below example shows how you can use the Init method and immediatly chain to the Run method:
package main
import "fmt"
func main() {
c := &Config{}
if err := c.Init(80).Run(); err != nil { // <- Method Chain: Init.Run
fmt.Println(err)
}
}
type Config struct {
port int
}
func (c *Config) Init(port int) *Config {
c.port = port
return c
}
func (c *Config) Run() error {
return fmt.Errorf("something went wrong...")
}package main
import "fmt"
func main() {
c := &Config{}
if err := c.Init(80).Run(); err != nil { // <- Method Chain: Init.Run
fmt.Println(err)
}
}
type Config struct {
port int
}
func (c *Config) Init(port int) *Config {
c.port = port
return c
}
func (c *Config) Run() error {
return fmt.Errorf("something went wrong...")
}Method Exercise (10 Mins)
package main
import "fmt"
type User struct {
First string
Last string
Email string
}
// Create a method on User called `FullName` that takes no arguments,
// and returns the First and Last name as a string
// Example User{First: "Rob", Last: "Pike", Email: "commander@google.com"} would return "Rob Pike"
// Create a method on User called `FormattedEmail` that takes no arguments,
// and returns a valid formatted email as a string
// Example User{First: "Rob", Last: "Pike", Email: "commander@google.com"} would return "Rob Pike <commander@google.com>"
// Create a method on User called `Reset` that sets all the fields to a blank string.
// Hint: we need to change the value so we need a special receiver for that...
// Extra Credit: do this in on line of code
func main() {
u := User{"Rob", "Pike", "commander@google.com"}
fmt.Printf("%+v\n", u)
fmt.Println("Full Name", u.FullName())
fmt.Println("Formatted Email", u.FormattedEmail())
u.Reset()
fmt.Printf("%+v\n", u)
}package main
import "fmt"
type User struct {
First string
Last string
Email string
}
// Create a method on User called `FullName` that takes no arguments,
// and returns the First and Last name as a string
// Example User{First: "Rob", Last: "Pike", Email: "commander@google.com"} would return "Rob Pike"
// Create a method on User called `FormattedEmail` that takes no arguments,
// and returns a valid formatted email as a string
// Example User{First: "Rob", Last: "Pike", Email: "commander@google.com"} would return "Rob Pike <commander@google.com>"
// Create a method on User called `Reset` that sets all the fields to a blank string.
// Hint: we need to change the value so we need a special receiver for that...
// Extra Credit: do this in on line of code
func main() {
u := User{"Rob", "Pike", "commander@google.com"}
fmt.Printf("%+v\n", u)
fmt.Println("Full Name", u.FullName())
fmt.Println("Formatted Email", u.FormattedEmail())
u.Reset()
fmt.Printf("%+v\n", u)
}Method Solution
package main
import "fmt"
type User struct {
First string
Last string
Email string
}
func (u User) FullName() string {
return u.First + " " + u.Last
}
func (u User) FormattedEmail() string {
return fmt.Sprintf("%s %s <%s>", u.First, u.Last, u.Email)
}
func (u *User) Reset() {
u.First = ""
u.Last = ""
u.Email = ""
}
func main() {
u := User{"Rob", "Pike", "commander@google.com"}
fmt.Printf("%+v\n", u)
fmt.Println("Full Name", u.FullName())
fmt.Println("Formatted Email", u.FormattedEmail())
u.Reset()
fmt.Printf("%+v\n", u)
}package main
import "fmt"
type User struct {
First string
Last string
Email string
}
func (u User) FullName() string {
return u.First + " " + u.Last
}
func (u User) FormattedEmail() string {
return fmt.Sprintf("%s %s <%s>", u.First, u.Last, u.Email)
}
func (u *User) Reset() {
u.First = ""
u.Last = ""
u.Email = ""
}
func main() {
u := User{"Rob", "Pike", "commander@google.com"}
fmt.Printf("%+v\n", u)
fmt.Println("Full Name", u.FullName())
fmt.Println("Formatted Email", u.FormattedEmail())
u.Reset()
fmt.Printf("%+v\n", u)
}Extra Credit Solution:
package main
import "fmt"
type User struct {
First string
Last string
Email string
}
func (u User) FullName() string {
return u.First + " " + u.Last
}
func (u User) FormattedEmail() string {
return fmt.Sprintf("%s %s <%s>", u.First, u.Last, u.Email)
}
func (u *User) Reset() {
*u = User{}
}
func main() {
u := User{"Rob", "Pike", "commander@google.com"}
fmt.Printf("%+v\n", u)
fmt.Println("Full Name", u.FullName())
fmt.Println("Formatted Email", u.FormattedEmail())
u.Reset()
fmt.Printf("%+v\n", u)
}package main
import "fmt"
type User struct {
First string
Last string
Email string
}
func (u User) FullName() string {
return u.First + " " + u.Last
}
func (u User) FormattedEmail() string {
return fmt.Sprintf("%s %s <%s>", u.First, u.Last, u.Email)
}
func (u *User) Reset() {
*u = User{}
}
func main() {
u := User{"Rob", "Pike", "commander@google.com"}
fmt.Printf("%+v\n", u)
fmt.Println("Full Name", u.FullName())
fmt.Println("Formatted Email", u.FormattedEmail())
u.Reset()
fmt.Printf("%+v\n", u)
}First Class Functions
In Go functions are first class citizens. That means we can treat functions as we do any other types.
- Function signatures are a
type - Functions can be arguments and return values to/from other functions
Closures
Functions close over their environment when they are defined. This means that a function has access to arguments declared before its declaration.
package main
import "fmt"
func main() {
name := "Ringo"
f := func() {
fmt.Printf("Hello %s", name)
}
f() // Hello Ringo
}Functions As Arguments
Functions in Go can be passed as values to other functions.
package main
import "fmt"
func main() {
f := func() string {
return "Hello"
}
sayHello(f)
}
func sayHello(fn func() string) {
fmt.Println(fn())
}package main
import "fmt"
func main() {
f := func() string {
return "Hello"
}
sayHello(f)
}
func sayHello(fn func() string) {
fmt.Println(fn())
}The signature of the function being passed in must match the function signature of the argument in the callee function.
Anonymous Functions
Anonymous functions are functions that aren’t assigned to a variable, or that don’t have a “name” associated with them.
sayHello(func() string {
return "Hello"
})
This is a very common pattern in Go, and one you should get comfortable seeing and using in Go code.
Functions As Types
Function signatures act as a defacto “type”, but explicit types can also be created for a function signature.
This has a few benefits:
- Argument declaration is cleaner
- The new type can have other functions hung off it
- The new type can now easily be documented
package main
import "fmt"
type greeter func() string
func main() {
sayHello(func() string {
return "Hello"
})
}
func sayHello(fn greeter) {
fmt.Println(fn())
}package main
import "fmt"
type greeter func() string
func main() {
sayHello(func() string {
return "Hello"
})
}
func sayHello(fn greeter) {
fmt.Println(fn())
}Great example of this: https://golang.org/pkg/net/http/#HandlerFunc
Functions On Functions
By creating a new type for a function, we can treat that type like all types in Go, and can hang other functions off of it.
type greeter func() string
func (greeter) Name() string {
return "World"
}
func sayHello(fn greeter) {
fmt.Println(fn(), fn.Name())
}
First Class Functions Exercise (10m)
Using the code below, refactor it to make it easier to read by using typed arguments and function variables.
package main
import (
"fmt"
"strings"
)
// TODO: declare a type called `replacer` that has the function signature for the second argument to the `replace` function below
// TODO: update the replace signature to take a `replacer` as the second argument
func replace(s string, r func(string) string) string {
var result string
for _, v := range s {
result = result + r(string(v))
}
return result
}
func main() {
// Capitalize each letter
s := replace("go programmers are gophers", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(s)
// replace all vowels with the $ sign
// TODO: Refactor the second argument to be stored as a variable (`r`) and pass in that variable as the second argument
s = replace("go programmers are gophers", func(s string) string {
switch s {
case "a", "e", "i", "o", "u":
return "$"
default:
return s
}
})
fmt.Println(s)
}package main
import (
"fmt"
"strings"
)
// TODO: declare a type called `replacer` that has the function signature for the second argument to the `replace` function below
// TODO: update the replace signature to take a `replacer` as the second argument
func replace(s string, r func(string) string) string {
var result string
for _, v := range s {
result = result + r(string(v))
}
return result
}
func main() {
// Capitalize each letter
s := replace("go programmers are gophers", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(s)
// replace all vowels with the $ sign
// TODO: Refactor the second argument to be stored as a variable (`r`) and pass in that variable as the second argument
s = replace("go programmers are gophers", func(s string) string {
switch s {
case "a", "e", "i", "o", "u":
return "$"
default:
return s
}
})
fmt.Println(s)
}The code currently works and has the following output:
GO PROGRAMMERS ARE GOPHERS
g$ pr$gr$mm$rs $r$ g$ph$rsGO PROGRAMMERS ARE GOPHERS
g$ pr$gr$mm$rs $r$ g$ph$rsFirst Class Functions Solution
While the functinality of the code did not change at all, using functions as types and variables can make your code clearner and easier to read.
package main
import (
"fmt"
"strings"
)
// TODO: declare a type called `replacer` that has the function signature for the second argument to the `replace` function below
type replacer func(string) string
// TODO: update the replace signature to take a `replacer` as the second argument
func replace(s string, r replacer) string {
var result string
for _, v := range s {
result = result + r(string(v))
}
return result
}
func main() {
// Capitalize each letter
s := replace("go programmers are gophers", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(s)
// replace all vowels with the $ sign
// TODO: Refactor the second argument to be stored as a variable (`r`) and pass in that variable as the second argument
r := func(s string) string {
switch s {
case "a", "e", "i", "o", "u":
return "$"
default:
return s
}
}
s = replace("go programmers are gophers", r)
fmt.Println(s)
}package main
import (
"fmt"
"strings"
)
// TODO: declare a type called `replacer` that has the function signature for the second argument to the `replace` function below
type replacer func(string) string
// TODO: update the replace signature to take a `replacer` as the second argument
func replace(s string, r replacer) string {
var result string
for _, v := range s {
result = result + r(string(v))
}
return result
}
func main() {
// Capitalize each letter
s := replace("go programmers are gophers", func(s string) string {
return strings.ToUpper(s)
})
fmt.Println(s)
// replace all vowels with the $ sign
// TODO: Refactor the second argument to be stored as a variable (`r`) and pass in that variable as the second argument
r := func(s string) string {
switch s {
case "a", "e", "i", "o", "u":
return "$"
default:
return s
}
}
s = replace("go programmers are gophers", r)
fmt.Println(s)
}Note, the output did not change:
GO PROGRAMMERS ARE GOPHERS
g$ pr$gr$mm$rs $r$ g$ph$rsGO PROGRAMMERS ARE GOPHERS
g$ pr$gr$mm$rs $r$ g$ph$rsDeferring Function Calls
The defer keyword in Go allows us to defer the execution of a function call until the return of the parent function.
package main
import "fmt"
func main() {
defer goodbye()
fmt.Println("hello")
}
func goodbye() {
fmt.Println("goodbye")
}package main
import "fmt"
func main() {
defer goodbye()
fmt.Println("hello")
}
func goodbye() {
fmt.Println("goodbye")
}hello
goodbye
Deferring With Multiple Returns
defer is useful for ensuring functions get called regardless of which path your code takes.
func loadPage() ([]byte, error) {
resp, err := http.Get("https://www.gopherguides.com")
if err != nil {
return nil, err
}
defer resp.Body.Close() // <<<
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("bad status: %d", resp.StatusCode)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return b, nil
}
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
)
func main() {
b, err := loadPage()
if err != nil {
log.Fatalln(err)
}
log.Println(string(b))
}
// section: loadpage
func loadPage() ([]byte, error) {
resp, err := http.Get("https://www.gopherguides.com")
if err != nil {
return nil, err
}
defer resp.Body.Close() // <<<
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("bad status: %d", resp.StatusCode)
}
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return b, nil
}
// section: loadpageDefer Call Order
The order of execution for deferred function calls is LIFO (last in, first out).
package main
import "fmt"
func main() {
defer fmt.Println("one")
defer fmt.Println("two")
defer fmt.Println("three")
}package main
import "fmt"
func main() {
defer fmt.Println("one")
defer fmt.Println("two")
defer fmt.Println("three")
}three
two
one
Deferred Calls And Panic
Deferred calls will still get called even if another deferred call panics.
package main
import "fmt"
func main() {
defer fmt.Println("one")
defer panic("two")
defer fmt.Println("three")
}package main
import "fmt"
func main() {
defer fmt.Println("one")
defer panic("two")
defer fmt.Println("three")
}three
one
panic: two
goroutine 1 [running]:
main.main()
main.go:9 +0x1ba
exit status 2
shell returned 1
You can recover from a panic as well. This will be covered in a later module.
Defers And Exit/Fatal
Deferred calls will not fire if the code explicitly calls os.Exit or
log.Fatal
package main
import (
"fmt"
"os"
)
func main() {
defer fmt.Println("one")
os.Exit(1)
defer fmt.Println("three")
}package main
import (
"fmt"
"os"
)
func main() {
defer fmt.Println("one")
os.Exit(1)
defer fmt.Println("three")
}
package main
import (
"fmt"
"log"
)
func main() {
defer fmt.Println("one")
log.Fatal("boom")
defer fmt.Println("three")
}Defer And Anonymous Functions
It is also common to combine cleanup tasks into a single defer using an anonymous function.
package main
import (
"fmt"
"io"
"os"
)
func main() {
if err := fileCopy("readme.txt", "readme-copy.txt"); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("file copied successfully")
}
func fileCopy(sourceName string, destName string) error {
src, err := os.Open(sourceName)
if err != nil {
return err
}
defer func() {
fmt.Println("closing", sourceName)
src.Close()
}()
dst, err := os.Create(destName)
if err != nil {
return err
}
defer func() {
fmt.Println("closing", destName)
dst.Close()
}()
if _, err := io.Copy(dst, src); err != nil {
return err
}
return nil
}package main
import (
"fmt"
"io"
"os"
)
func main() {
if err := fileCopy("readme.txt", "readme-copy.txt"); err != nil {
fmt.Println(err)
os.Exit(1)
}
fmt.Println("file copied successfully")
}
func fileCopy(sourceName string, destName string) error {
src, err := os.Open(sourceName)
if err != nil {
return err
}
defer func() {
fmt.Println("closing", sourceName)
src.Close()
}()
dst, err := os.Create(destName)
if err != nil {
return err
}
defer func() {
fmt.Println("closing", destName)
dst.Close()
}()
if _, err := io.Copy(dst, src); err != nil {
return err
}
return nil
}Defer And Scope
It is important to understand scope when using defer. If we take the following code, the output is a bit unexpected:
func main() {
now := time.Now()
defer fmt.Printf("time since program started: %s\n", time.Since(now))
fmt.Println("sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
}
package main
import (
"fmt"
"time"
)
// section: main
func main() {
now := time.Now()
defer fmt.Printf("time since program started: %s\n", time.Since(now))
fmt.Println("sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
}
// section: main
/*
// section: output
sleeping for 2 seconds...
time since program started: 157ns
// section: output
*/Output:
package main
import (
"fmt"
"time"
)
// section: main
func main() {
now := time.Now()
defer fmt.Printf("time since program started: %s\n", time.Since(now))
fmt.Println("sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
}
// section: main
/*
// section: output
sleeping for 2 seconds...
time since program started: 157ns
// section: output
*/The reason that we don’t see the proper elapsed time, is because we didn’t capture a scope to the now variable to use later in the defer function. Because of this, when the defer is added to the stack, it captures the value of now when the defer is scheduled, which in effect will have no elapsed time when it runs later.
Fixing Defer And Scope
To make sure your variables are evaluated when a defer executes, and not when they are scheduled, you need to scope them via an anonymous function.
func main() {
now := time.Now()
// use an anonymous function to scope variables to be used in the defer
defer func(now time.Time) {
fmt.Printf("time since program started: %s\n", time.Since(now))
}(now)
fmt.Println("sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
}
package main
import (
"fmt"
"time"
)
// section: main
func main() {
now := time.Now()
// use an anonymous function to scope variables to be used in the defer
defer func(now time.Time) {
fmt.Printf("time since program started: %s\n", time.Since(now))
}(now)
fmt.Println("sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
}
// section: main
/*
// section: output
sleeping for 2 seconds...
time since program started: 2.004430822s
// section: output
*/Output:
package main
import (
"fmt"
"time"
)
// section: main
func main() {
now := time.Now()
// use an anonymous function to scope variables to be used in the defer
defer func(now time.Time) {
fmt.Printf("time since program started: %s\n", time.Since(now))
}(now)
fmt.Println("sleeping for 2 seconds...")
time.Sleep(2 * time.Second)
}
// section: main
/*
// section: output
sleeping for 2 seconds...
time since program started: 2.004430822s
// section: output
*/When the defer function runs, it will be passed the current value of now, instead of evaluating the value of now when the defer is scheduled.
Properly Closing Files
In Go, the rule is to never ignore an error. However, when you defer the Close on most types in Go, there is no easy way to handle the error.
As such, there are a couple of idioms that have become standard in the Go ecosystem.
The first is to explicitly call Close even if it’s deferred. They reason for this is that the documents in Go specify that any call to a Close function should be idempotent, and calling it more than once will be ok.
In the following code sample, the Close is deferred. In the event that the code returns early, it’s because you already encountered an error, and likely the Close is going to also encounter an error related to the same issue, and therefore is not important:
func closeit() error {
f, err := os.Create("foo")
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write([]byte("bar")); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}
package main
import (
"log"
"os"
)
func main() {
if err := closeit(); err != nil {
log.Fatal(err)
}
}
// section: func
func closeit() error {
f, err := os.Create("foo")
if err != nil {
return err
}
defer f.Close()
if _, err := f.Write([]byte("bar")); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}
// section: funcThere are other factors that go beyond the scope of this training, but there is a great article that goes in depth on the topic.
Common Mistake: HTTP Response Body Leaks
When working with HTTP clients, failing to properly close response bodies is one of the most common mistakes in Go. This can lead to resource leaks and connection pool exhaustion.
The Problem: Deferring Close Too Late
A common mistake is to defer the Close() call after checking the response status code:
func fetchBad(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
defer resp.Body.Close() // too late; early returns leak the body
_, err = io.ReadAll(resp.Body)
return err
}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
type trackingBody struct {
io.ReadCloser
closed bool
}
func (t *trackingBody) Close() error {
t.closed = true
return t.ReadCloser.Close()
}
type stubRoundTripper struct {
status int
body *trackingBody
}
func (s stubRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: s.status,
Body: s.body,
Header: make(http.Header),
Request: &http.Request{},
}, nil
}
func newClient(status int) (*http.Client, *trackingBody) {
body := &trackingBody{ReadCloser: io.NopCloser(strings.NewReader("ok"))}
return &http.Client{Transport: stubRoundTripper{status: status, body: body}}, body
}
// section: bad
func fetchBad(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
defer resp.Body.Close() // too late; early returns leak the body
_, err = io.ReadAll(resp.Body)
return err
}
// section: bad
// section: good
func fetchGood(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // always defer immediately
if resp.StatusCode != http.StatusOK {
// drain the body so the transport can reuse the connection
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
return nil
}
// section: good
// section: main
func main() {
badClient, badBody := newClient(http.StatusServiceUnavailable)
err := fetchBad(badClient)
fmt.Printf("bad version error: %v\n", err)
fmt.Printf("bad version closed body: %t\n", badBody.closed)
goodClient, goodBody := newClient(http.StatusServiceUnavailable)
err = fetchGood(goodClient)
fmt.Printf("good version error: %v\n", err)
fmt.Printf("good version closed body: %t\n", goodBody.closed)
}
// section: main
/*
// section: output
bad version error: unexpected status: 503
bad version closed body: false
good version error: unexpected status: 503
good version closed body: true
// section: output
*/If the status code check fails, the function returns early and resp.Body.Close() is never called, causing a resource leak.
The Solution: Defer Immediately After Error Check
The correct pattern is to defer Close() immediately after checking that the HTTP request didn’t return an error:
func fetchGood(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // always defer immediately
if resp.StatusCode != http.StatusOK {
// drain the body so the transport can reuse the connection
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
return nil
}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
type trackingBody struct {
io.ReadCloser
closed bool
}
func (t *trackingBody) Close() error {
t.closed = true
return t.ReadCloser.Close()
}
type stubRoundTripper struct {
status int
body *trackingBody
}
func (s stubRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: s.status,
Body: s.body,
Header: make(http.Header),
Request: &http.Request{},
}, nil
}
func newClient(status int) (*http.Client, *trackingBody) {
body := &trackingBody{ReadCloser: io.NopCloser(strings.NewReader("ok"))}
return &http.Client{Transport: stubRoundTripper{status: status, body: body}}, body
}
// section: bad
func fetchBad(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
defer resp.Body.Close() // too late; early returns leak the body
_, err = io.ReadAll(resp.Body)
return err
}
// section: bad
// section: good
func fetchGood(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // always defer immediately
if resp.StatusCode != http.StatusOK {
// drain the body so the transport can reuse the connection
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
return nil
}
// section: good
// section: main
func main() {
badClient, badBody := newClient(http.StatusServiceUnavailable)
err := fetchBad(badClient)
fmt.Printf("bad version error: %v\n", err)
fmt.Printf("bad version closed body: %t\n", badBody.closed)
goodClient, goodBody := newClient(http.StatusServiceUnavailable)
err = fetchGood(goodClient)
fmt.Printf("good version error: %v\n", err)
fmt.Printf("good version closed body: %t\n", goodBody.closed)
}
// section: main
/*
// section: output
bad version error: unexpected status: 503
bad version closed body: false
good version error: unexpected status: 503
good version closed body: true
// section: output
*/This ensures the response body is always closed, regardless of which return path is taken.
Why This Matters: Connection Pool Implications
Go’s HTTP client maintains a connection pool for performance. When you don’t close the response body:
- Connections remain open and can’t be reused
- Connection pool gets exhausted after enough requests
- New requests block waiting for available connections
- Memory leaks as resources aren’t released
Even for error responses (4xx, 5xx), the body must be closed to return the connection to the pool.
Best Practice: Read And Discard Error Response Bodies
For connection reuse, you should also read and discard the body on error responses:
func fetchGood(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // always defer immediately
if resp.StatusCode != http.StatusOK {
// drain the body so the transport can reuse the connection
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
return nil
}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
type trackingBody struct {
io.ReadCloser
closed bool
}
func (t *trackingBody) Close() error {
t.closed = true
return t.ReadCloser.Close()
}
type stubRoundTripper struct {
status int
body *trackingBody
}
func (s stubRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: s.status,
Body: s.body,
Header: make(http.Header),
Request: &http.Request{},
}, nil
}
func newClient(status int) (*http.Client, *trackingBody) {
body := &trackingBody{ReadCloser: io.NopCloser(strings.NewReader("ok"))}
return &http.Client{Transport: stubRoundTripper{status: status, body: body}}, body
}
// section: bad
func fetchBad(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
defer resp.Body.Close() // too late; early returns leak the body
_, err = io.ReadAll(resp.Body)
return err
}
// section: bad
// section: good
func fetchGood(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // always defer immediately
if resp.StatusCode != http.StatusOK {
// drain the body so the transport can reuse the connection
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
return nil
}
// section: good
// section: main
func main() {
badClient, badBody := newClient(http.StatusServiceUnavailable)
err := fetchBad(badClient)
fmt.Printf("bad version error: %v\n", err)
fmt.Printf("bad version closed body: %t\n", badBody.closed)
goodClient, goodBody := newClient(http.StatusServiceUnavailable)
err = fetchGood(goodClient)
fmt.Printf("good version error: %v\n", err)
fmt.Printf("good version closed body: %t\n", goodBody.closed)
}
// section: main
/*
// section: output
bad version error: unexpected status: 503
bad version closed body: false
good version error: unexpected status: 503
good version closed body: true
// section: output
*/Using io.Copy(io.Discard, resp.Body) ensures:
- The connection can be reused
- No memory is wasted storing the error body
- The connection pool remains healthy
Complete Example
Here’s a complete example showing the proper pattern:
func main() {
badClient, badBody := newClient(http.StatusServiceUnavailable)
err := fetchBad(badClient)
fmt.Printf("bad version error: %v\n", err)
fmt.Printf("bad version closed body: %t\n", badBody.closed)
goodClient, goodBody := newClient(http.StatusServiceUnavailable)
err = fetchGood(goodClient)
fmt.Printf("good version error: %v\n", err)
fmt.Printf("good version closed body: %t\n", goodBody.closed)
}
package main
import (
"fmt"
"io"
"net/http"
"strings"
)
type trackingBody struct {
io.ReadCloser
closed bool
}
func (t *trackingBody) Close() error {
t.closed = true
return t.ReadCloser.Close()
}
type stubRoundTripper struct {
status int
body *trackingBody
}
func (s stubRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: s.status,
Body: s.body,
Header: make(http.Header),
Request: &http.Request{},
}, nil
}
func newClient(status int) (*http.Client, *trackingBody) {
body := &trackingBody{ReadCloser: io.NopCloser(strings.NewReader("ok"))}
return &http.Client{Transport: stubRoundTripper{status: status, body: body}}, body
}
// section: bad
func fetchBad(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
defer resp.Body.Close() // too late; early returns leak the body
_, err = io.ReadAll(resp.Body)
return err
}
// section: bad
// section: good
func fetchGood(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // always defer immediately
if resp.StatusCode != http.StatusOK {
// drain the body so the transport can reuse the connection
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
return nil
}
// section: good
// section: main
func main() {
badClient, badBody := newClient(http.StatusServiceUnavailable)
err := fetchBad(badClient)
fmt.Printf("bad version error: %v\n", err)
fmt.Printf("bad version closed body: %t\n", badBody.closed)
goodClient, goodBody := newClient(http.StatusServiceUnavailable)
err = fetchGood(goodClient)
fmt.Printf("good version error: %v\n", err)
fmt.Printf("good version closed body: %t\n", goodBody.closed)
}
// section: main
/*
// section: output
bad version error: unexpected status: 503
bad version closed body: false
good version error: unexpected status: 503
good version closed body: true
// section: output
*/Output:
bad version error: unexpected status: 503
bad version closed body: false
good version error: unexpected status: 503
good version closed body: truepackage main
import (
"fmt"
"io"
"net/http"
"strings"
)
type trackingBody struct {
io.ReadCloser
closed bool
}
func (t *trackingBody) Close() error {
t.closed = true
return t.ReadCloser.Close()
}
type stubRoundTripper struct {
status int
body *trackingBody
}
func (s stubRoundTripper) RoundTrip(_ *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: s.status,
Body: s.body,
Header: make(http.Header),
Request: &http.Request{},
}, nil
}
func newClient(status int) (*http.Client, *trackingBody) {
body := &trackingBody{ReadCloser: io.NopCloser(strings.NewReader("ok"))}
return &http.Client{Transport: stubRoundTripper{status: status, body: body}}, body
}
// section: bad
func fetchBad(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
defer resp.Body.Close() // too late; early returns leak the body
_, err = io.ReadAll(resp.Body)
return err
}
// section: bad
// section: good
func fetchGood(client *http.Client) error {
resp, err := client.Get("https://api.example.com/users/1")
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close() // always defer immediately
if resp.StatusCode != http.StatusOK {
// drain the body so the transport can reuse the connection
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
_, err = io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("read body: %w", err)
}
return nil
}
// section: good
// section: main
func main() {
badClient, badBody := newClient(http.StatusServiceUnavailable)
err := fetchBad(badClient)
fmt.Printf("bad version error: %v\n", err)
fmt.Printf("bad version closed body: %t\n", badBody.closed)
goodClient, goodBody := newClient(http.StatusServiceUnavailable)
err = fetchGood(goodClient)
fmt.Printf("good version error: %v\n", err)
fmt.Printf("good version closed body: %t\n", goodBody.closed)
}
// section: main
/*
// section: output
bad version error: unexpected status: 503
bad version closed body: false
good version error: unexpected status: 503
good version closed body: true
// section: output
*/Remember: Always defer resp.Body.Close() immediately after checking the error from the HTTP request, before any other status checks.
Named Returns, Defer, And Scope
Now that we have a better understanding of functions, defer, and scope, this is another example of where named returns can be very difficult to determine behavior.
Remember, when writing code in Go, readability is very important. This code does not do what most people would expect.
Before this code is run, who can tell me what the value will be?
package main
import (
"fmt"
)
func main() {
fmt.Println(MeaningOfLife())
}
func MeaningOfLife() (rc int) {
defer func() { rc++ }()
return 41
}Defer Exercise (10 Mins)
Using defer, only have one statement to close the file, and ensure
that regardless of what branch of code you exit from, the file will be closed.
Extra Credit: Wrap your defer in an anonymous function and print out “Closing File” in addition to closing the actual file.
package main
import (
"io"
"os"
)
func main() {
if err := write("readme.txt", "This is a readme file"); err != nil {
panic(err)
}
}
func write(fileName string, text string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
_, err = io.WriteString(file, text)
if err != nil {
file.Close()
return err
}
file.Close()
return nil
}package main
import (
"io"
"os"
)
func main() {
if err := write("readme.txt", "This is a readme file"); err != nil {
panic(err)
}
}
func write(fileName string, text string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
_, err = io.WriteString(file, text)
if err != nil {
file.Close()
return err
}
file.Close()
return nil
}Defer Solution
package main
import (
"io"
"os"
)
func main() {
if err := write("readme.txt", "This is a readme file"); err != nil {
panic(err)
}
}
func write(fileName string, text string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
_, err = io.WriteString(file, text)
if err != nil {
return err
}
// explicitly close the file
if err := file.Close(); err != nil {
return err
}
return nil
}package main
import (
"io"
"os"
)
func main() {
if err := write("readme.txt", "This is a readme file"); err != nil {
panic(err)
}
}
func write(fileName string, text string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
_, err = io.WriteString(file, text)
if err != nil {
return err
}
// explicitly close the file
if err := file.Close(); err != nil {
return err
}
return nil
}Extra credit:
package main
import (
"fmt"
"io"
"os"
)
func main() {
if err := write("readme.txt", "This is a readme file"); err != nil {
panic(err)
}
}
func write(fileName string, text string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer func() {
fmt.Printf("closing %s\n", file.Name())
file.Close()
}()
_, err = io.WriteString(file, text)
if err != nil {
return err
}
// explicitly close the file
if err := file.Close(); err != nil {
return err
}
return nil
}package main
import (
"fmt"
"io"
"os"
)
func main() {
if err := write("readme.txt", "This is a readme file"); err != nil {
panic(err)
}
}
func write(fileName string, text string) error {
file, err := os.Create(fileName)
if err != nil {
return err
}
defer func() {
fmt.Printf("closing %s\n", file.Name())
file.Close()
}()
_, err = io.WriteString(file, text)
if err != nil {
return err
}
// explicitly close the file
if err := file.Close(); err != nil {
return err
}
return nil
}Init
Any time you declare an init() function, Go will load and run it prior to anything else in that package.
package main
import (
"fmt"
"time"
)
var weekday string
func init() {
weekday = time.Now().Weekday().String()
}
func main() {
fmt.Printf("Today is %s", weekday)
}Multiple Init Statements
Unlike the main() function that can only be declared once, the init() function can be declared multiple times throughout a package. However, multiple init()s can make it difficult to know which one has priority over the others. In this section, we will show how to maintain control over multiple init() statements.
In most cases, init() functions will execute in the order that you encounter them.
package main
import "fmt"
func init() {
fmt.Println("First init")
}
func init() {
fmt.Println("Second init")
}
func init() {
fmt.Println("Third init")
}
func init() {
fmt.Println("Fourth init")
}
func main() {}
// section: code
package main
import "fmt"
func init() {
fmt.Println("First init")
}
func init() {
fmt.Println("Second init")
}
func init() {
fmt.Println("Third init")
}
func init() {
fmt.Println("Fourth init")
}
func main() {}
// section: code
/*
// section: output
First init
Second init
Third init
Fourth init
// section: output
*/Output:
// section: code
package main
import "fmt"
func init() {
fmt.Println("First init")
}
func init() {
fmt.Println("Second init")
}
func init() {
fmt.Println("Third init")
}
func init() {
fmt.Println("Fourth init")
}
func main() {}
// section: code
/*
// section: output
First init
Second init
Third init
Fourth init
// section: output
*/Init Order
If you have multiple init() declarations in a package in different files, the compiler will run them based on the order in which it loads files.
Given the following directory structure, if you had an init() declaration in a.go and b.go, the first init() to run would be from a.go. However, if you renamed a.go to c.go, the init() from b.go would now run first.
Care must be taken if any init() declarations have an order of precedence when running and exist in different files.
└── cmd
├── a.go
├── b.go
└── main.go
Using Init() For Side Effects
In Go, it is sometimes desirable to import a package not for its content, but for the side effects that occur upon importing the package. This often means that there is an init() statement in the imported code that executes before any of the other code, allowing for the developer to manipulate the state in which their program is starting. This technique is called importing for a side effect.
A common use case for importing for side effects is to register functionality in your code, which lets a package know what part of the code your program needs to use. In the image package, for example, the image.Decode function needs to know which format of image it is trying to decode (jpg, png, gif, etc.) before it can execute. You can accomplish this by first importing a specific program that has an init() statement side effect.
Let’s say you are trying to use image.Decode on a .png file with the following code snippet:
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
package main
import (
"fmt"
"image"
_ "image/png"
"io"
"log"
)
func main() {
fmt.Println("vim-go")
}
// section: decode
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
// section: decodeA program with this code will still compile, but any time we try to decode a png image, we will get an error.
To fix this, we would need to first register an image format for image.Decode. Luckily, the image/png package contains the following init() statement:
func init() {
image.RegisterFormat("png", pngHeader, Decode, DecodeConfig)
}
Therefore, if we import image/png into our decoding snippet, then the image.RegisterFormat() function in image/png will run before any of our code:
package main
import (
"fmt"
"image"
_ "image/png"
"io"
"log"
)
func main() {
fmt.Println("vim-go")
}
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
package main
import (
"fmt"
"image"
_ "image/png"
"io"
"log"
)
func main() {
fmt.Println("vim-go")
}
// section: decode
func decode(reader io.Reader) image.Rectangle {
m, _, err := image.Decode(reader)
if err != nil {
log.Fatal(err)
}
return m.Bounds()
}
// section: decodeThis will set the state and register that we require the png version of image.Decode(). This registration will happen as a side effect of importing image/png.
Init Exercise (10 Mins)
package main
import (
"fmt"
"os"
"strings"
"time"
)
var version = "0.1"
func main() {
// #TODO: Move all the logic to parse name to the `init` function
var name = ""
name = os.Args[0]
if strings.Contains(name, string(os.PathSeparator)) {
s := strings.Split(name, string(os.PathSeparator))
name = s[len(s)-1]
}
fmt.Printf("%s version %s starting at %s\n", name, version, time.Now().Format(time.Kitchen))
}package main
import (
"fmt"
"os"
"strings"
"time"
)
var version = "0.1"
func main() {
// #TODO: Move all the logic to parse name to the `init` function
var name = ""
name = os.Args[0]
if strings.Contains(name, string(os.PathSeparator)) {
s := strings.Split(name, string(os.PathSeparator))
name = s[len(s)-1]
}
fmt.Printf("%s version %s starting at %s\n", name, version, time.Now().Format(time.Kitchen))
}Init Solution
package main
import (
"fmt"
"os"
"strings"
"time"
)
var version = "0.1"
var name string
func init() {
name = os.Args[0]
if strings.Contains(name, string(os.PathSeparator)) {
s := strings.Split(name, string(os.PathSeparator))
name = s[len(s)-1]
}
}
func main() {
fmt.Printf("%s version %s starting at %s\n", name, version, time.Now().Format(time.Kitchen))
}package main
import (
"fmt"
"os"
"strings"
"time"
)
var version = "0.1"
var name string
func init() {
name = os.Args[0]
if strings.Contains(name, string(os.PathSeparator)) {
s := strings.Split(name, string(os.PathSeparator))
name = s[len(s)-1]
}
}
func main() {
fmt.Printf("%s version %s starting at %s\n", name, version, time.Now().Format(time.Kitchen))
}