Advanced

Structs and Methods

Create custom types with structs and attach behavior with methods.

What you will see

0

About 2 min of reading

Try It

You will build a Person type, attach methods that read or mutate it, and see the difference between value and pointer receivers when a method changes data.

1

I declare a struct with a string name and an integer age, create a value of it, and print the name. The struct bundles two fields into one named type.

package main import "fmt" type Person struct { Name string Age int } func main() { p := Person{Name: "Ada", Age: 30} fmt.Println(p.Name) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Ada
2

I print both fields together to confirm the value carries its data. Each field is accessed with dot notation, so p.Age gives me the integer stored inside.

package main import "fmt" type Person struct { Name string Age int } func main() { p := Person{Name: "Ada", Age: 30} fmt.Println(p.Name, p.Age) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Ada 30
3

I attach a method to the struct that returns a greeting string built from the name. The receiver (p Person) gives the method access to the value it is called on.

package main import "fmt" type Person struct { Name string Age int } func (p Person) Greet() string { return "Hello, " + p.Name } func main() { p := Person{Name: "Ada", Age: 30} fmt.Println(p.Greet()) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
Hello, Ada
A value receiver works on a copy, so Greet can read fields but cannot change the original struct.
4

I add a pointer-receiver method that increments the age. Pointers let a method mutate the original value, so after calling Birthday the age is now 31.

package main import "fmt" type Person struct { Name string Age int } func (p *Person) Birthday() { p.Age++ } func main() { p := Person{Name: "Ada", Age: 30} p.Birthday() fmt.Println(p.Age) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
31
Use the pointer receiver (*Person) only when a method must modify the struct. A value receiver gets a copy and cannot change the caller's value.
5

I compare the two receiver styles with a counter. A value-receiver method that increments a field has no effect on the original, which stays at its starting value.

package main import "fmt" type Counter struct { N int } func (c Counter) Inc() { c.N++ } func main() { c := Counter{} c.Inc() fmt.Println(c.N) }

Before you run it, say out loud what it will print. Then open the answer.

What it prints
0
The method ran on a copy, so c.N is still 0. Increment the original by using a pointer receiver instead.

Data Flow

A struct holds fields; methods operate on a copy or the original through a receiver.
1 Create a struct value
↓
2 Call a method with a receiver
↓
3 Value receiver: copy is used
↓
4 Pointer receiver: original is mutated
Why this works
Struct tags

Tags add metadata to fields:

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

Tags are read by packages like encoding/json. They let the JSON field names differ from the Go field names.

When you have a choice

Value vs pointer receivers

Choose the receiver type carefully:

func (p Person) Info() string     // value copy
func (p *Person) Update() {}       // mutates original

If the method mutates the struct, use a pointer receiver. Go lets you call pointer methods on addressable values automatically.

Examples

Defining a struct

Grouping fields

type Person struct {
    Name string
    Age  int
}

p := Person{Name: "Ada", Age: 30}
fmt.Println(p.Name)

A struct bundles related data into one type. Person has a Name and an Age.

Why it works: the type ... struct declaration defines a new named type.

Writing a method

Attaching behavior

func (p Person) Greet() string {
    return "Hello, " + p.Name
}

fmt.Println(p.Greet())

A method has a receiver (p Person). It is like a function that knows which value it belongs to. The receiver works like this in other languages.

Why it works: methods are declared with the receiver syntax before the function name.

Pointers to structs

Mutating through pointers

func (p *Person) Birthday() {
    p.Age++
}

p.Birthday()
fmt.Println(p.Age)   // 31

A pointer receiver *Person lets the method change the original value. Value receivers get a copy and cannot mutate the caller.

Watch out: use a pointer receiver when the method must modify the struct.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Slices and Arrays
Previous Lesson
Slices and Arrays
Next Lesson
Interfaces
Interfaces →