Go Struct

A struct in Go is a composite type that groups together zero or more named fields of possibly different types, giving you a way to model real-world records like a user, a product, or a coordinate.

Defining a Struct

You define a struct using the type and struct keywords, followed by a list of field names and their types. Struct definitions are usually placed at package level so they can be reused across your program. Field names conventionally start with an uppercase letter if you want them visible (exported) outside the package, or lowercase if they should stay private to the package.

Defining and using a simple struct

package main

import "fmt"

type Person struct {
	Name string
	Age  int
	City string
}

func main() {
	var p Person
	p.Name = "Elena"
	p.Age = 29
	p.City = "Lisbon"

	fmt.Println(p.Name, p.Age, p.City)
}
Note: A zero-value struct has every field set to its type's zero value: empty string for string, 0 for int, false for bool, and so on. Go never leaves struct fields uninitialized or garbage-filled.

Struct Literals

Rather than creating a variable and assigning fields one by one, you can construct a fully populated struct in a single expression called a struct literal. There are two common styles: positional literals, where values are supplied in field order, and keyed literals, where each value is paired explicitly with its field name.

Keyed vs positional struct literals

package main

import "fmt"

type Point struct {
	X int
	Y int
}

func main() {
	// Keyed literal - order does not matter, and it is self-documenting
	a := Point{X: 3, Y: 4}

	// Positional literal - order must match the struct definition exactly
	b := Point{5, 12}

	fmt.Println(a, b)
}
Note: Positional literals break silently if someone reorders the struct's fields later, since the compiler has no way to know which value was meant for which field. Prefer keyed literals for anything beyond a two-field struct.

Nested Structs and Pointers

Structs can contain other structs as fields, letting you build up richer models such as an Order that embeds a Customer. Go also lets you take the address of a struct with & to get a pointer, which is useful when you want a function to modify the original struct instead of a copy.

Nested struct with a pointer receiver

package main

import "fmt"

type Address struct {
	City    string
	Country string
}

type Customer struct {
	Name    string
	Address Address
}

func relocate(c *Customer, newCity string) {
	c.Address.City = newCity
}

func main() {
	cust := Customer{
		Name:    "Marco",
		Address: Address{City: "Turin", Country: "Italy"},
	}

	relocate(&cust, "Milan")
	fmt.Println(cust.Address.City)
}

Structs vs Other Composite Types

It helps to know when a struct is the right tool compared to a map or a slice of separate variables.

FeatureStructMap
Field typesCan differ per fieldAll values share one type
Keys known at compile timeYesNo, keys are dynamic
Access speedFixed offset, very fastHash lookup
Best forFixed-shape recordsDynamic, variable-key data
  • Use a struct when the set of fields is known ahead of time and each field has a distinct meaning.
  • Give exported fields a capital letter if other packages need to read or write them.
  • Prefer keyed struct literals so field order changes never introduce silent bugs.
  • Pass a pointer to a struct when a function needs to mutate the caller's copy.
  • Group related structs (like Address inside Customer) instead of flattening everything into one giant type.
Note: You can compare two struct values directly with == as long as every field is itself comparable (no slices, maps, or functions inside). This makes structs handy as map keys too.

Exercise: Go Struct

How do you access a field named Age on a struct instance p?