Structs
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:
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}
{Desk lamp 129.5 3}
Desk lamp 129.5
| Part | What it is |
|---|---|
type Product struct { … } | Defines a new type called Product, outside main. |
Name string | A field: the name first, then the type. |
Product{Name: "Desk lamp", …} | A struct literal: a value for each field, by name. |
lamp.Name | A 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:
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}
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:
| Verb | Shows |
|---|---|
%v | the default form, same as Println |
%+v | a struct with its field names |
%d | a whole number |
%s | a string |
%q | a string, in double quotes |
%.2f | a decimal number with two digits after the point (%.1f for one) |
%T | the type |
Use a field that doesn't exist and the compiler catches it right away:
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}
# 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:
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}
{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:
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}
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:
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}
Stock value: 708.5
The p that range gives you is a copy of the element too. Take a guess:
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}
Each round's p is a copy of the element, so the copy gets set to 0 and the elements in the slice don't change. To change an element in the slice, you have to go through its index.
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):
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}
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.
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}
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.
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.
12 30
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}
The most expensive product
✓Finish mostExpensive so it returns the product with the highest price in the slice. The program looks in two shops.
Desk lamp 129.5 Mug 25
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}
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.
Pen 15 Notebook 12
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}
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;%+vshows the field names. - Assigning a struct, passing it to a function and the value
rangegives 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.