Lesson 26. A few pointers

After reading lesson 26, you’ll be able to

  • Declare and use pointers
  • Understand the relationship between pointers and random access memory (RAM)
  • Know when to use—and not use—pointers

Walk around any neighborhood and you’ll likely encounter homes with individual addresses and street signs to guide you on your way. You may happen upon a closed-down shop with an apologetic sign: “Sorry, we’ve moved!” Pointers are a bit like the sign in the store window that directs you to a different address.

A pointer is a variable that points to the address of another variable. In computer science, pointers are a form of indirection, and indirection can be a powerful tool.

All problems in computer science can be solved by another level of indirection...

David Wheeler

Pointers are quite useful, but over the years they’ve been associated with a great deal of angst. Languages in the past—C in particular—had little emphasis on safety. Many crashes and security vulnerabilities can be tied back to the misuse of pointers. This gave rise to several languages that don’t expose pointers to programmers.

Go does have pointers, but with an emphasis on memory safety. Go isn’t plagued with issues like dangling pointers. This would be like heading to the address for your favorite shop, only to find it was accidentally replaced with the parking lot for a new casino.

If you’ve encountered pointers before, take a deep breath. This isn’t going to be so bad. If this is your first encounter, relax. Go is a safe place to learn pointers.

Consider this

Like the shop sign directing visitors to a new address, pointers direct a computer where to look for a value. What’s another situation where you’re directed to look somewhere else?

26.1. The ampersand and the asterisk

Pointers in Go adopt the well-established syntax used by C. There are two symbols to be aware of, the ampersand (&) and the asterisk (*), though the asterisk serves a dual purpose, as you’ll soon see.

The address operator, represented by an ampersand, determines the address of a variable in memory. Variables store their values in a computer’s RAM, and the location where a value is stored is known as its memory address. The following listing prints a memory address as a hexadecimal number, though the address on your computer will differ.

Listing 26.1. Address operator: memory.go
answer := 42
fmt.Println(&answer)         1

  • 1 Prints 0x1040c108

This is the location in memory where the computer stored 42. Thankfully, you can use the variable name answer to retrieve the value, rather than the memory address your computer uses.

Note

You can’t take the address of a literal string, number, or Boolean. The Go compiler will report an error for &42 or &"another level of indirection".

The address operator (&) provides the memory address of a value. The reverse operation is known as dereferencing, which provides the value that a memory address refers to. The following listing dereferences the address variable by prefixing it with an asterisk (*).

Listing 26.2. Dereference operator: memory.go
answer := 42
fmt.Println(&answer)       1

address := &answer
fmt.Println(*address)      2

  • 1 Prints 0x1040c108
  • 2 Prints 42

In the preceding listing and in figure 26.1, the address variable holds the memory address of answer. It doesn’t hold the answer (42), but it knows where to find it.

Note

Memory addresses in C can be manipulated with pointer arithmetic (for example address++), but Go disallows unsafe operations.

Figure 26.1. address points to answer

Quick check 26.1

1

What does fmt.Println(*&answer) display for listing 26.2?

2

How might the Go compiler know the difference between dereferencing and multiplication?

QC 26.1 answer

1

It prints 42 because the memory address (&) is dereferenced (*) back to the value.

2

Multiplication is an infix operator requiring two values, whereas dereferencing prefixes a single variable.

 

26.1.1. Pointer types

Pointers store memory addresses.

The address variable in listing 26.2 is a pointer of type *int, as the %T format verb reveals in the following listing.

Listing 26.3. A pointer type: type.go
answer := 42
address := &answer

fmt.Printf("address is a %T
", address)      1

  • 1 Prints address is a *int

The asterisk in *int denotes that the type is a pointer. In this case, it can point to other variables of type int.

Pointer types can appear anywhere types are used, including in variable declarations, function parameters, return types, structure field types, and so on. In the following listing, the asterisk (*) in the declaration of home indicates that it’s a pointer type.

Listing 26.4. Declaring a pointer: home.go
canada := "Canada"

var home *string
fmt.Printf("home is a %T
", home)       1

home = &canada
fmt.Println(*home)                       2

  • 1 Prints home is a *string
  • 2 Prints Canada
Tip

An asterisk prefixing a type denotes a pointer type, whereas an asterisk prefixing a variable name is used to dereference the value that variable points to.

The home variable in the previous listing can point at any variable of type string. However, the Go compiler won’t allow home to point to a variable of any other type, such as int.

Note

The C type system is easily convinced that a memory address holds a different type. That can be useful at times but, once again, Go avoids potentially unsafe operations.

Quick check 26.2

1

What code would you use to declare a variable named address that can point to integers?

2

How can you distinguish between the declaration of a pointer type and dereferencing a pointer in listing 26.4?

QC 26.2 answer

1

var address *int

2

An asterisk prefixing a type denotes a pointer type, whereas an asterisk prefixing a variable name is used to dereference the value that variable points to.

 

26.2. Pointers are for pointing

Charles Bolden became the administrator of NASA on July 17, 2009. He was preceded by Christopher Scolese. By representing the administrator role with a pointer, the following listing can point administrator at whoever fills the role (see figure 26.2).

Listing 26.5. Administrator for NASA: nasa.go
var administrator *string

scolese := "Christopher J. Scolese"
administrator = &scolese
fmt.Println(*administrator)          1

bolden := "Charles F. Bolden"
administrator = &bolden
fmt.Println(*administrator)          2

  • 1 Prints Christopher J. Scolese
  • 2 Prints Charles F. Bolden
Figure 26.2. administrator points to bolden

Changes to the value of bolden can be made in one place, because the administrator variable points to bolden rather than storing a copy:

bolden = "Charles Frank Bolden Jr."
fmt.Println(*administrator)          1

  • 1 Prints Charles Frank Bolden Jr.

It’s also possible to dereference administrator to change the value of bolden indirectly:

*administrator = "Maj. Gen. Charles Frank Bolden Jr."
fmt.Println(bolden)                                    1

  • 1 Prints Maj. Gen. Charles Frank Bolden Jr.

Assigning major to administrator results in a new pointer that’s also pointing at the bolden string (see figure 26.3):

major := administrator
*major = "Major General Charles Frank Bolden Jr."
fmt.Println(bolden)                                1

  • 1 Prints Major General Charles Frank Bolden Jr.
Figure 26.3. administrator and major point to bolden

The major and administrator pointers both hold the same memory address and therefore are equal:

fmt.Println(administrator == major)          1

  • 1 Prints true

Charles Bolden was succeeded by Robert M. Lightfoot Jr. on January 20, 2017. After this change, administrator and major no longer point to the same memory address (see figure 26.4):

lightfoot := "Robert M. Lightfoot Jr."
administrator = &lightfoot
fmt.Println(administrator == major)          1

  • 1 Prints false
Figure 26.4. administrator now points to lightfoot

Assigning the dereferenced value of major to another variable makes a copy of the string. After the clone is made, direct and indirect modifications to bolden have no effect on the value of charles, or vice versa:

charles := *major
*major = "Charles Bolden"
fmt.Println(charles)          1
fmt.Println(bolden)           2

  • 1 Prints Major General Charles Frank Bolden Jr.
  • 2 Prints Charles Bolden

If two variables contain the same string, they’re considered equal, as with charles and bolden in the following code. This is the case even though they have different memory addresses:

charles = "Charles Bolden"
fmt.Println(charles == bolden)           1
fmt.Println(&charles == &bolden)         2

  • 1 Prints true
  • 2 Prints false

In this section, the value of bolden was modified indirectly by dereferencing the administrator and major pointers. This demonstrates what pointers can do, though it would be straightforward to assign values directly to bolden in this instance.

Quick check 26.3

1

What’s the benefit of using a pointer in listing 26.5?

2

Describe what the statements major := administrator and charles := *major do.

QC 26.3 answer

1

Changes can be made in one place, as the administrator variable points to a person rather than storing a copy.

2

The variable major is a new *string pointer that holds the same memory address as administrator, whereas charles is a string containing a copy of the value that major was pointing to.

 

26.2.1. Pointing to structures

Pointers are frequently used with structures. As such, the Go language designers chose to provide a few ergonomic amenities for pointers to structures.

Unlike strings and numbers, composite literals can be prefixed with an address operator. In the following listing, the timmy variable holds a memory address pointing to a person structure.

Listing 26.6. Person structure: struct.go
type person struct {
    name, superpower string
    age              int
}

timmy := &person{
    name: "Timothy",
    age:  10,
}

Furthermore, it isn’t necessary to dereference structures to access their fields. The following listing is preferable to writing (*timmy).superpower.

Listing 26.7. Composite literals: struct.go
timmy.superpower = "flying"

fmt.Printf("%+v
", timmy)          1

  • 1 Prints &{name:Timothy superpower:flying age:10}
Quick check 26.4

1

What are valid uses of the address operator?

  1. Literal strings: &"Timothy"
  2. Literal integers: &10
  3. Composite literals: &person{name: "Timothy"}
  4. All of the above

2

What’s the difference between timmy.superpower and (*timmy).superpower?

QC 26.4 answer

1

The address operator is valid with variable names and composite literals, but not literal strings or numbers.

2

There’s no functional difference because Go automatically dereferences pointers for fields, but timmy.superpower is easier to read and is therefore preferable.

 

26.2.2. Pointing to arrays

As with structures, composite literals for arrays can be prefixed with the address operator (&) to create a new pointer to an array. Arrays also provide automatic dereferencing, as shown in the following listing.

Listing 26.8. Pointer to an array: superpowers.go
superpowers := &[3]string{"flight", "invisibility", "super strength"}

fmt.Println(superpowers[0])            1
fmt.Println(superpowers[1:2])          2

  • 1 Prints flight
  • 2 Prints [invisibility]

The array in the previous listing is dereferenced automatically when indexing or slicing it. There’s no need to write the more cumbersome (*superpowers)[0].

Note

Unlike the C language, arrays and pointers in Go are completely independent types.

Composite literals for slices and maps can also be prefixed with the address operator (&), but there’s no automatic dereferencing.

Quick check 26.5

Q1:

What’s another way to write (*superpowers)[2:] where superpowers is a pointer to an array?

QC 26.5 answer

1:

Writing superpowers[2:] is the same, thanks to automatic dereferencing for arrays.

 

26.3. Enabling mutation

Pointers are used to enable mutation across function and method boundaries.

26.3.1. Pointers as parameters

Function and method parameters are passed by value in Go. That means functions always operate on a copy of passed arguments. When a pointer is passed to a function, the function receives a copy of the memory address. By dereferencing the memory address, a function can mutate the value a pointer points to.

In listing 26.9 a birthday function is declared with one parameter of type *person. This allows the function body to dereference the pointer and modify the value it points to. As with listing 26.7, it isn’t necessary to explicitly dereference the p variable to access the age field. The syntax in the following listing is preferable to (*p).age++.

Listing 26.9. Function parameters: birthday.go
type person struct {
    name, superpower string
    age              int
}

func birthday(p *person) {
    p.age++
}

The birthday function requires the caller to pass a pointer to a person, as shown in the following listing.

Listing 26.10. Function arguments: birthday.go
rebecca := person{
    name:       "Rebecca",
    superpower: "imagination",
    age:        14,
}

birthday(&rebecca)

fmt.Printf("%+v
", rebecca)         1

  • 1 Prints {name:Rebecca superpower:imagination age:15}
Quick check 26.6

1

What code would return Timothy 11? Refer to listing 26.6.

  1. birthday(&timmy)
  2. birthday(timmy)
  3. birthday(*timmy)

2

What age would Rebecca be if the birthday(p person) function didn’t use a pointer?

QC 26.6 answer

1

The timmy variable is a pointer already, so the correct answer is b. birthday(timmy).

2

Rebecca would forever remain 14 if birthday didn’t utilize a pointer.

 

26.3.2. Pointer receivers

Method receivers are similar to parameters. The birthday method in the next listing uses a pointer for the receiver, which allows the method to mutate a person’s attributes. This behavior is just like the birthday function in listing 26.9.

Listing 26.11. Pointer receiver: method.go
type person struct {
    name string
    age  int
}

func (p *person) birthday() {
    p.age++
}

In the following listing, declaring a pointer and calling the birthday method increments Terry’s age.

Listing 26.12. Method call with a pointer: method.go
terry := &person{
    name: "Terry",
    age:  15,
}
terry.birthday()
fmt.Printf("%+v
", terry)        1

  • 1 Prints &{name:Terry age:16}

Alternatively, the method call in the next listing doesn’t use a pointer, yet it still works. Go will automatically determine the address of (&) a variable when calling methods with dot notation, so you don’t need to write (&nathan).birthday().

Listing 26.13. Method call without a pointer: method.go
nathan := person{
    name: "Nathan",
    age:  17,
}
nathan.birthday()
fmt.Printf("%+v
", nathan)           1

  • 1 Prints {name:Nathan age:18}

Whether called with a pointer or not, the birthday method declared in listing 26.11 must specify a pointer receiver—otherwise, age wouldn’t increment.

Structures are frequently passed around with pointers. It makes sense for the birthday method to mutate a person’s attributes rather than create a whole new person. That said, not every structure should be mutated. The standard library provides a great example in the time package. The methods of the time.Time type never use a pointer receiver, preferring to return a new time instead, as shown in the next listing. After all, tomorrow is a new day.

Listing 26.14. Tomorrow is a new day: day.go
const layout = "Mon, Jan 2, 2006"

day := time.Now()
tomorrow := day.Add(24 * time.Hour)

fmt.Println(day.Format(layout))                  1
fmt.Println(tomorrow.Format(layout))             2

  • 1 Prints Tue, Nov 10, 2009
  • 2 Prints Wed, Nov 11, 2009
Tip

You should use pointer receivers consistently. If some methods need pointer receivers, use pointer receivers for all methods of the type (see golang.org/doc/faq#methods_on_values_or_pointers).

Quick check 26.7

Q1:

How do you know that time.Time never uses a pointer receiver?

QC 26.7 answer

1:

The code in listing 26.14 doesn’t reveal whether or not the Add method uses a pointer receiver because dot notation is the same either way. It’s best to look at the documentation for the methods of time.Time (see golang.org/pkg/time/#Time).

 

26.3.3. Interior pointers

Go provides a handy feature called interior pointers, used to determine the memory address of a field inside of a structure. The levelUp function in the following listing mutates a stats structure and therefore requires a pointer.

Listing 26.15. The levelUp function: interior.go
type stats struct {
    level             int
    endurance, health int
}

func levelUp(s *stats) {
    s.level++
    s.endurance = 42 + (14 * s.level)
    s.health = 5 * s.endurance
}

The address operator in Go can be used to point to a field within a structure, as shown in the next listing.

Listing 26.16. Interior pointers: interior.go
type character struct {
    name  string
    stats stats
}

player := character{name: "Matthias"}
levelUp(&player.stats)

fmt.Printf("%+v
", player.stats)          1

  • 1 Prints {level:1 endurance:56 health:280}

The character type doesn’t have any pointers in the structure definition, yet you can take the memory address of any field when the need arises. The code &player.stats provides a pointer to the interior of the structure.

Quick check 26.8

Q1:

What’s an interior pointer?

QC 26.8 answer

1:

A pointer that points at a field inside a structure. This is achieved by using the address operator on a field of a structure, such as &player.stats.

 

26.3.4. Mutating arrays

Though slices tend to be preferred over arrays, using arrays can be appropriate when there’s no need to change their length. The chessboard from lesson 16 is such an example. The following listing demonstrates how pointers allow functions to mutate elements of the array.

Listing 26.17. Resetting the chessboard: array.go
func reset(board *[8][8]rune) {
    board[0][0] = 'r'
    // ...
}

func main() {
    var board [8][8]rune
    reset(&board)

    fmt.Printf("%c", board[0][0])         1
}

  • 1 Prints r

In lesson 20, the suggested implementation for Conway’s Game of Life makes use of slices even though the world is a fixed size. Armed with pointers, you could rewrite the Game of Life to use arrays.

Quick check 26.9

Q1:

When is it appropriate to use a pointer to an array?

QC 26.9 answer

1:

Arrays are appropriate for data with fixed dimensions, such as a chess-board. Arrays are copied when passed to functions or methods unless a pointer is used, which enables mutation.

 

26.4. Pointers in disguise

Not all mutations require explicit use of a pointer. Go uses pointers behind the scenes for some of the built-in collections.

26.4.1. Maps are pointers

Lesson 19 states that maps aren’t copied when assigned or passed as arguments. Maps are pointers in disguise, so pointing to a map is redundant. Don’t do this:

func demolish(planets *map[string]string)        1

  • 1 Unnecessary pointer

It’s perfectly fine for the key or value of a map to be a pointer type, but there’s rarely a reason to point to a map.

Quick check 26.10

Q1:

Is a map a pointer?

QC 26.10 answer

1:

Yes, even though maps don’t resemble pointers syntactically, they are in fact pointers. There’s no way to use a map that isn’t a pointer.

 

26.4.2. Slices point at arrays

Lesson 17 describes a slice as a window into an array. To point at an element of the array, slices use a pointer.

A slice is represented internally as a structure with three elements: a pointer to an array, the capacity of the slice, and the length. The internal pointer allows the underlying data to be mutated when a slice is passed directly to a function or method.

An explicit pointer to a slice is only useful when modifying the slice itself: the length, capacity, or starting offset. In the following listing, the reclassify function modifies the length of the planets slice. The calling function (main) wouldn’t see this change if reclassify didn’t utilize a pointer.

Listing 26.18. Modifying a slice: slice.go
func reclassify(planets *[]string) {
    *planets = (*planets)[0:8]
}

func main() {
    planets := []string{
        "Mercury", "Venus", "Earth", "Mars",
        "Jupiter", "Saturn", "Uranus", "Neptune",
        "Pluto",
    }
    reclassify(&planets)

    fmt.Println(planets)           1
}

  • 1 Prints [Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune]

Instead of mutating the passed slice as in listing 26.18, an arguably cleaner approach is to write the reclassify function to return a new slice.

Quick check 26.11

Q1:

Functions and methods wanting to mutate the data they receive will require a pointer for which two data types?

QC 26.11 answer

1:

Structures and arrays.

 

26.5. Pointers and interfaces

The following listing demonstrates that both martian and a pointer to martian satisfy the talker interface.

Listing 26.19. Pointers and interfaces: martian.go
type talker interface {
    talk() string
}

func shout(t talker) {
    louder := strings.ToUpper(t.talk())
    fmt.Println(louder)
}

type martian struct{}

func (m martian) talk() string {
    return "nack nack"
}

func main() {
    shout(martian{})          1
    shout(&martian{})         1
}

  • 1 Prints NACK NACK

It’s different when methods use pointer receivers, as shown in the following listing.

Listing 26.20. Pointers and interfaces: interface.go
type laser int

func (l *laser) talk() string {
    return strings.Repeat("pew ", int(*l))
}

func main() {
    pew := laser(2)
    shout(&pew)           1
}

  • 1 Prints PEW PEW

In the preceding listing, &pew is of type *laser, which satisfies the talker interface that shout requires. But shout(pew) doesn’t work because laser doesn’t satisfy the interface in this case.

Quick check 26.12

Q1:

When does a pointer satisfy an interface?

QC 26.12 answer

1:

A pointer to a value satisfies all the interfaces that the non-pointer version of the type satisfies.

 

26.6. Use pointers wisely

Pointers can be useful, but they also add complexity. It can be more difficult to follow code when values could be changed from multiple places.

Use pointers when it makes sense, but don’t overuse them. Programming languages that don’t expose pointers often use them behind the scenes, such as when composing a class of several objects. With Go you decide when to use pointers and when to not use them.

Quick check 26.13

Q1:

Why shouldn’t pointers be overused?

QC 26.13 answer

1:

Code that doesn’t use pointers may be simpler to understand.

 

Summary

  • Pointers store memory addresses.
  • The address operator (&) provides the memory address of a variable.
  • A pointer can be dereferenced (*) to access or modify the value it points to.
  • Pointers are types declared with a preceding asterisk, such as *int.
  • Use pointers to mutate values across function and method boundaries.
  • Pointers are most useful with structures and arrays.
  • Maps and slices use pointers behind the scenes.
  • Interior pointers can point at fields inside structures without declaring those fields as pointers.
  • Use pointers when it makes sense but don’t overuse them.

Let’s see if you got this...

Experiment: turtle.go

Write a program with a turtle that can move up, down, left, or right. The turtle should store an (x, y) location where positive values go down and to the right. Use methods to increment/decrement the appropriate variable. A main function should exercise the methods you’ve written and print the final location.

Tip

Method receivers will need to use pointers in order to manipulate the x and y values.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset