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.