Kodewick
Start learning →
Lesson 10 of 24Contents

Structs

Lesson 10 of 24 · Part 3 · Types · about 15 minutes

A product has a name, a price and a stock count. Three separate variables would work, but with many products it gets messy. A struct groups related values together into a new type:

prog.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price float648	Stock int9}10 11func main() {12	lamp := Product{Name: "Desk lamp", Price: 129.5, Stock: 3}13	fmt.Println(lamp)14	fmt.Println(lamp.Name, lamp.Price)15}
Output
{Desk lamp 129.5 3}
Desk lamp 129.5
PartWhat it is
type Product struct { … }Defines a new type called Product, outside main.
Name stringA field: the name first, then the type.
Product{Name: "Desk lamp", …}A struct literal: a value for each field, by name.
lamp.NameA dot gets one field.

When fmt.Println shows a whole struct, it puts the field values in curly braces, in order. Field names usually start with a capital letter: a name that starts with a capital can be used from other packages too; a lowercase one only inside its own package.

Reading and changing fields

Fields work like variables: you can read them and change them. %+v shows a struct with its field names, which is handy when you're tracking down a problem:

prog.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price float648	Stock int9}10 11func main() {12	lamp := Product{Name: "Desk lamp", Price: 129.5, Stock: 3}13	lamp.Stock--14	lamp.Price = 9915	fmt.Println(lamp.Stock)16	fmt.Printf("%v\n", lamp)17	fmt.Printf("%+v\n", lamp)18}
Output
2
{Desk lamp 99 2}
{Name:Desk lamp Price:99 Stock:2}

lamp.Stock-- takes 1 off the stock, just like with a variable. You used Printf with %T in lesson 2; the quoted text can hold other "verbs" too. The common ones:

VerbShows
%vthe default form, same as Println
%+va struct with its field names
%da whole number
%sa string
%qa string, in double quotes
%.2fa decimal number with two digits after the point (%.1f for one)
%Tthe type

Use a field that doesn't exist and the compiler catches it right away:

prog.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price float648	Stock int9}10 11func main() {12	lamp := Product{Name: "Desk lamp", Price: 129.5, Stock: 3}13	fmt.Println(lamp.Color)14}
Output
# command-line-arguments
./prog.go:13:19: lamp.Color undefined (type Product has no field or method Color)

Product has no field called Color (and no method by that name either; methods are lesson 12). A misspelled field name gives the same error.

Zero values

A field you don't give a value gets the zero value of its type: 0 for numbers, "" for strings. A struct declared with var has every field at its zero value:

prog.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price float648	Stock int9}10 11type Point struct {12	X, Y int13}14 15func main() {16	var empty Product17	fmt.Printf("%+v\n", empty)18	pen := Product{Name: "Pen"}19	fmt.Printf("%+v\n", pen)20	p := Point{3, 4}21	fmt.Println(p.X + p.Y)22}
Output
{Name: Price:0 Stock:0}
{Name:Pen Price:0 Stock:0}
7

X, Y int is two fields of the same type on one line. Point{3, 4} leaves out the field names and gives the values in order; then you must give every field. With more fields that's easy to get wrong, so you'll usually write the names.

Assigning makes a copy

Assign a struct to another variable and you get a whole copy. Change the copy and the original stays the same. Press Step and see that a and b are two separate boxes:

prog.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price float648	Stock int9}10 11func main() {12	a := Product{Name: "Desk lamp", Price: 129.5, Stock: 3}13	b := a14	b.Stock = 1015	fmt.Println(a.Stock, b.Stock)16}
Output
3 10

Passing a struct to a function works the same way: the function gets a copy, and changing it doesn't touch the caller's struct. To let a function change the original, you need a pointer: that's the next lesson.

Slices of structs

Many products go into a []Product. Inside the curly braces you can leave out the type name for each element:

prog.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price float648	Stock int9}10 11func main() {12	items := []Product{13		{Name: "Desk lamp", Price: 129.5, Stock: 3},14		{Name: "Pen", Price: 4, Stock: 50},15		{Name: "Notebook", Price: 12, Stock: 10},16	}17	total := 0.018	for _, p := range items {19		total += p.Price * float64(p.Stock)20	}21	fmt.Println("Stock value:", total)22}
Output
Stock value: 708.5

The p that range gives you is a copy of the element too. Take a guess:

Guess first

What does this show?

1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Stock int8}9 10func main() {11	items := []Product{{Name: "Pen", Stock: 5}, {Name: "Notebook", Stock: 2}}12	for _, p := range items {13		p.Stock = 014	}15	fmt.Println(items[0].Stock, items[1].Stock)16}

Sorting by a field

sort.Slice sorts in whatever order you want. Besides the slice, you give it a small function that answers "should element i come before element j?" (passing functions as values is lesson 16):

prog.go
1package main2 3import (4	"fmt"5	"sort"6)7 8type Product struct {9	Name  string10	Price float6411}12 13func main() {14	items := []Product{15		{Name: "Desk lamp", Price: 129.5},16		{Name: "Pen", Price: 4},17		{Name: "Notebook", Price: 12},18	}19	sort.Slice(items, func(i, j int) bool {20		return items[i].Price < items[j].Price21	})22	for _, p := range items {23		fmt.Println(p.Name, p.Price)24	}25}
Output
Pen 4
Notebook 12
Desk lamp 129.5

Cheapest first. Change < to > for the most expensive first, or use items[i].Name < items[j].Name to sort by name.

Embedding

A field written as just a type, with no name, is embedded: the fields of the struct inside can be used directly.

prog.go
1package main2 3import "fmt"4 5type Address struct {6	City   string7	Street string8}9 10type Employee struct {11	Name string12	Address13}14 15func main() {16	e := Employee{Name: "Maya", Address: Address{City: "Austin", Street: "12 Oak St"}}17	fmt.Println(e.City)18	fmt.Printf("%+v\n", e)19}
Output
Austin
{Name:Maya Address:{City:Austin Street:12 Oak St}}

e.City is short for e.Address.City. With %+v you can see that Address is still a whole field.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Area of a rectangle

Finish area so it returns the area of the rectangle (width times height). The program shows the area of two rectangles.

It should print
12
30
exercise1.go
1package main2 3import "fmt"4 5type Rect struct {6	Width, Height int7}8 9func area(r Rect) int {10	return 011}12 13func main() {14	fmt.Println(area(Rect{Width: 3, Height: 4}))15	fmt.Println(area(Rect{Width: 5, Height: 6}))16}
Output
Press Run or Ctrl+Enter
Exercise 2

The most expensive product

Finish mostExpensive so it returns the product with the highest price in the slice. The program looks in two shops.

It should print
Desk lamp 129.5
Mug 25
exercise2.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Price float648}9 10func mostExpensive(items []Product) Product {11	best := items[0]12	return best13}14 15func main() {16	shop := []Product{{Name: "Pen", Price: 4}, {Name: "Desk lamp", Price: 129.5}, {Name: "Notebook", Price: 12}}17	other := []Product{{Name: "Spoon", Price: 8}, {Name: "Mug", Price: 25}}18	a := mostExpensive(shop)19	fmt.Println(a.Name, a.Price)20	b := mostExpensive(other)21	fmt.Println(b.Name, b.Price)22}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: the restock that didn't happen

Each product should get 10 more in stock, but the program shows the same stock as before. Fix it so it shows Pen 15 and Notebook 12 on two lines.

It should print
Pen 15
Notebook 12
exercise3.go
1package main2 3import "fmt"4 5type Product struct {6	Name  string7	Stock int8}9 10func main() {11	items := []Product{{Name: "Pen", Stock: 5}, {Name: "Notebook", Stock: 2}}12	for _, p := range items {13		p.Stock += 1014	}15	for _, p := range items {16		fmt.Println(p.Name, p.Stock)17	}18}
Output
Press Run or Ctrl+Enter

What you learned

  • type Name struct { … } defines a struct type; write literals with field names: Product{Name: "Pen", Price: 4}.
  • A dot reads or changes a field: p.Price = 99; fields you don't set are zero values; %+v shows the field names.
  • Assigning a struct, passing it to a function and the value range gives you are all whole copies.
  • To change a struct in a slice, use the index: items[i].Stock += 10.
  • sort.Slice(s, func(i, j int) bool { … }) sorts by a field.
  • Embedding: a field that is just a type name; its fields can be used directly.
Finished this lesson?Mark it. Progress is saved in this browser, and you can undo it.