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.