Structs and methods in Go

Learn to define structs in Go, use fields and struct literals, embed structs, and write value and pointer receiver methods, with a runnable example.

Prerequisites

Learning objectives

  • Define structs and build values with named and positional literals
  • Attach methods and choose between value and pointer receivers
  • Reuse fields and methods with embedded structs

A struct groups related values into a single named type. It is how you model a “thing” in Go — a user, a rectangle, an HTTP request. Methods then attach behaviour to that type.

Defining a struct

You declare a struct with type ... struct and list its fields with a name and a type. Exported fields (and types) start with a capital letter.

type User struct {
	Name string
	Age  int
}

Creating struct values

You build a value with a struct literal. Prefer named fields: the code stays readable and keeps working if fields are reordered. A struct declared without a literal gets the zero value of every field.

u1 := User{Name: "Ada", Age: 36} // named fields (preferred)
u2 := User{"Alan", 41}           // positional: order matters
var u3 User                      // zero values: "" and 0

u1.Age = 37 // read and update a field with a dot

Methods

A method is a function with a receiver written before its name. The receiver binds the function to the type, so you call it as value.Method().

func (u User) Greeting() string {
	return "Hi, I'm " + u.Name
}

Value vs pointer receivers

A value receiver (u User) works on a copy, so it cannot change the original. A pointer receiver (u *User) works on the actual value, so it can mutate fields. Use a pointer receiver when the method must modify the struct, or when the struct is large and copying it would be wasteful.

// Reads only: a value receiver is fine.
func (u User) Greeting() string {
	return "Hi, I'm " + u.Name
}

// Mutates the struct: needs a pointer receiver.
func (u *User) Birthday() {
	u.Age++
}

Embedded structs

Embedding one struct inside another (by writing the type with no field name) promotes its fields and methods to the outer type. It is Go’s lightweight take on composition — favour it over deep inheritance hierarchies.

type Animal struct {
	Name string
}

type Dog struct {
	Animal // embedded
	Breed  string
}

d := Dog{Animal: Animal{Name: "Rex"}, Breed: "Labrador"}
fmt.Println(d.Name) // promoted from Animal: "Rex"

A complete example

The program below defines a Rectangle with a read-only Area method and a mutating Scale method. It compiles and runs as-is.

package main

import "fmt"

// Rectangle groups a width and a height under one type.
type Rectangle struct {
	Width  float64
	Height float64
}

// Area returns the rectangle's area. A value receiver is enough
// because the method only reads the fields.
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

// Scale multiplies both sides in place. It needs a pointer receiver
// so the change is visible to the caller.
func (r *Rectangle) Scale(factor float64) {
	r.Width *= factor
	r.Height *= factor
}

func main() {
	r := Rectangle{Width: 3, Height: 4}
	fmt.Printf("area: %.1f\n", r.Area())

	r.Scale(2)
	fmt.Printf("scaled area: %.1f\n", r.Area())
}

Running it prints:

area: 12.0
scaled area: 48.0

Common mistakes

  • Expecting a value receiver to mutate. It works on a copy; the caller’s struct is untouched. Switch to a pointer receiver.
  • Mixing receiver kinds on one type. Pick pointer receivers for a type that needs any mutation, and use them everywhere for that type.
  • Comparing structs with slices or maps inside. Structs are comparable with == only when all their fields are; a struct containing a slice or map is not.
  • Relying on positional literals. They break silently when fields are added or reordered. Name the fields.

Practice

Define a Counter struct with a count field and an Increment method that uses a pointer receiver, then increment it three times and print the total.

Show the solution
package main

import "fmt"

// Counter holds a running total.
type Counter struct {
	count int
}

// Increment adds one. The pointer receiver lets it mutate the value.
func (c *Counter) Increment() {
	c.count++
}

func main() {
	c := Counter{}
	c.Increment()
	c.Increment()
	c.Increment()
	fmt.Println(c.count)
}

Because Increment uses a pointer receiver, each call updates the same Counter, so the program prints 3. With a value receiver it would print 0.

Summary

  • A struct groups named fields into one type; build values with named literals.
  • Methods attach behaviour through a receiver written before the method name.
  • Value receivers work on a copy; pointer receivers can mutate the struct.
  • Embedding promotes fields and methods and is Go’s form of composition.

Next, learn how types share behaviour through interfaces in Go. If maps are still fresh in your mind, see how they pair with structs in maps in Go.

Your progress is stored only in this browser. No account is required.

Search

Search runs entirely in your browser.