Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

6. Structs and Methods

A struct groups named fields into one value. A method is a function attached to a type. Between them they cover the ground that classes cover in other languages, and they do it with two mechanisms instead of one, the trade Go makes throughout.

6.1 Structs

// Struct literals, methods, and comparison.
package main

import (
	"fmt"
	"math"
)

type Point struct {
	X, Y float64
}

// Add returns the sum of p and q. The receiver p is a copy.
func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }

// Dist returns the distance from p to the origin.
func (p Point) Dist() float64 { return math.Hypot(p.X, p.Y) }

func main() {
	a := Point{3, 4}       // positional
	b := Point{X: 1, Y: 1} // by name
	var c Point            // zero value
	fmt.Println(a, b, c)   // {3 4} {1 1} {0 0}
	fmt.Println(a.Add(b))  // {4 5}
	fmt.Printf("%.1f\n", a.Dist())
	fmt.Println(a == Point{3, 4}) // structs compare field by field
}
{3 4} {1 1} {0 0}
{4 5}
5.0
true

A composite literal fills the fields. The positional form Point{3, 4} requires every field in declaration order, and it breaks silently in meaning — though not in compilation — the day somebody reorders the struct, so keep it for small types whose field order is part of their definition, like a point, and use Point{X: 1, Y: 1} everywhere else. The named form may also omit fields, which then take their zero values.

var c Point gives you a Point with both fields zero. There is no constructor to call and no allocation to think about, and Go’s convention is to make that zero value mean something sensible so callers can skip the ceremony.

Structs are comparable with == when all their fields are, comparing field by field, and usable as map keys on the same condition. A struct containing a slice is not comparable.

Fields whose names begin with a capital letter are visible outside the package and the rest are not, the same rule as everywhere else in Go. That is also what determines which fields encoding/json will marshal, and the backtick string after a field, the struct tag, is how you tell it the name to use:

type User struct {
	Name  string `json:"name"`
	Email string `json:"email,omitempty"`
}

An anonymous struct declares a type and a value at once, and is the right tool for a one-off grouping that deserves no name. Table-driven tests are built out of them, and Chapter 10 shows that.

cases := []struct {
	in   string
	want int
}{
	{"", 0},
	{"a b", 2},
}

6.2 Methods

A method is a function with a receiver, declared between func and the name.

func (p Point) Dist() float64 { return math.Hypot(p.X, p.Y) }

Inside, p is an ordinary parameter that happens to be written first. The receiver’s type must be a named type declared in the same package, which stops you adding methods to int or to somebody else’s type, and it need not be a struct:

// A String method changes how fmt prints a value.
package main

import "fmt"

type Weekday int

const (
	Sunday Weekday = iota
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)

var names = [...]string{"Sunday", "Monday", "Tuesday", "Wednesday",
	"Thursday", "Friday", "Saturday"}

func (d Weekday) String() string {
	if d < Sunday || d > Saturday {
		return fmt.Sprintf("Weekday(%d)", int(d))
	}
	return names[d]
}

func (d Weekday) IsWeekend() bool { return d == Sunday || d == Saturday }

func main() {
	fmt.Println(Wednesday, Saturday, Weekday(9))
	fmt.Println(Wednesday.IsWeekend(), Saturday.IsWeekend())
}
Wednesday Saturday Weekday(9)
false true

Weekday is an int with methods. The String method is special by convention rather than by language rule: fmt looks for it on any value it is asked to print, so defining one changes how the type appears everywhere, including inside slices, maps, and structs that contain it. Chapter 9 names the interface it satisfies. Note that String handles a value outside the valid range instead of panicking on the array index, because a String method that panics turns a debugging session into a worse debugging session.

6.3 Pointer Receivers

// A value receiver gets a copy; a pointer receiver gets the original.
package main

import "fmt"

type Counter struct{ N int }

func (c Counter) IncValue()    { c.N++ } // modifies the copy
func (c *Counter) IncPointer() { c.N++ } // modifies the caller's Counter

func main() {
	c := Counter{}
	c.IncValue()
	c.IncValue()
	fmt.Println(c.N) // 0

	c.IncPointer() // shorthand for (&c).IncPointer()
	c.IncPointer()
	fmt.Println(c.N) // 2
}
0
2

A value receiver gets a copy, so IncValue increments something that is thrown away when it returns. A pointer receiver gets the address, so IncPointer increments the caller’s Counter. This is section 5.2 with different punctuation, and it produces the same class of quiet bug.

c.IncPointer() is shorthand for (&c).IncPointer(), which Go inserts because c is addressable. The reverse also works: given a *Counter, calling a value-receiver method dereferences automatically. So the calls look identical at the call site and only the declaration tells you which you have.

The rule for choosing:

Use a pointer receiver when the method modifies the receiver, or when the type contains a sync.Mutex or anything else that must not be copied, or when the struct is genuinely large.

Be consistent within a type. If any method needs a pointer receiver, give them all pointer receivers, even the ones that only read. Mixing them means readers have to check each method, and it interacts badly with interfaces: Chapter 9 shows that a Counter value does not satisfy an interface whose methods have pointer receivers, while a *Counter satisfies both kinds.

Since the zero value should be useful, Go has no constructors. When a type needs setup, write a function named New or NewThing returning the value or a pointer to it, and that is a convention with no support from the language.

6.4 Embedding

Declaring a field with a type and no name embeds it. The outer type gets the inner type’s fields and methods as if they were its own.

// Embedding promotes the inner type's fields and methods to the outer one.
package main

import "fmt"

type Animal struct {
	Name string
	Legs int
}

func (a Animal) Describe() string {
	return fmt.Sprintf("%s has %d legs", a.Name, a.Legs)
}

type Dog struct {
	Animal // embedded: no field name
	Breed  string
}

// Dog overrides Describe by declaring its own.
func (d Dog) Describe() string {
	return d.Animal.Describe() + ", and is a " + d.Breed
}

func main() {
	d := Dog{Animal{"Rex", 4}, "collie"}
	fmt.Println(d.Name)              // promoted field
	fmt.Println(d.Legs)              // promoted field
	fmt.Println(d.Animal.Describe()) // the inner method
	fmt.Println(d.Describe())        // the outer one
}
Rex
4
Rex has 4 legs
Rex has 4 legs, and is a collie

d.Name works because Name was promoted from the embedded Animal, and so was Describe until Dog declared its own, which shadows it. The inner one is still reachable by its full path, d.Animal.Describe(), and that is how Dog builds on it. Promotion stops at the first match, so a name declared on the outer type always wins.

This looks like inheritance and it is not. Here is the difference:

// Embedding is not inheritance: the inner type never calls the outer one.
package main

import "fmt"

type Animal struct{ Name string }

func (a Animal) Speak() string { return "..." }
func (a Animal) Greet() string { return a.Name + " says " + a.Speak() }

type Dog struct{ Animal }

func (d Dog) Speak() string { return "woof" }

func main() {
	d := Dog{Animal{"Rex"}}
	fmt.Println(d.Speak()) // woof
	fmt.Println(d.Greet()) // Rex says ... -- not "woof"
}
woof
Rex says ...

Greet is declared on Animal, so its receiver is an Animal, and the a.Speak() inside it calls Animal.Speak. It cannot call Dog.Speak, because by the time Greet runs there is no Dog in sight — only the embedded Animal that was copied into the call. There is no virtual dispatch and no super. An embedded type does not know it has been embedded.

If you came here expecting a base class to call an overridden method, that is the thing Go declines to provide, and the answer is an interface: define the behavior as an interface, have Greet take one, and pass the Dog. Chapter 9.

What embedding does buy you is composition without writing forwarding methods by hand. Embedding a sync.Mutex gives your type Lock and Unlock methods directly. Embedding an interface in a struct gives you a type that satisfies it while overriding one method. And embedding one interface in another is how io.ReadWriter is built out of io.Reader and io.Writer.

Exercise 6-1. Give Point a Scale(f float64) method that multiplies both fields in place. Decide which receiver it needs and check by calling it.

Exercise 6-2. Write type Temperature float64 with a String method printing one decimal and a degree sign. Put one in a struct and print the struct.

Exercise 6-3. Write a Stack of ints with Push, Pop and Len. Pop should use the comma-ok form. Make the zero value a usable empty stack.

Exercise 6-4. Add a Speak method to Animal in the last program that Greet calls, then try to make d.Greet() print woof without changing Greet’s receiver type. Convince yourself it cannot be done.