09 - Hardwired to self de-struct
Covered in this module:
- structs
- methods
structs
Go structs are a way of collecting related items into an object/record:
type character struct {
name string
class string
level int
}
func main() {
char := character{name: "Helmut Van Kaiser", class: "Fighter", level: 4}
fmt.Println(char)
}
Struct properties are mutable and can be accessed with dot notation:
char := character{name: "Helmut Van Kaiser", class: "Fighter", level: 4}
fmt.Println(char.name)
fmt.Println(char.class)
fmt.Println(char.level)
char.level++
fmt.Println(char.level)
prints
Helmut Van Kaiser
Fighter
4
5
Struct properties are not required at initialization and are zero-valued by default:
char := character{name: "Helmut Van Kaiser"}
fmt.Println(char.name)
fmt.Println(char.class)
fmt.Println(char.level)
prints
Helmut Van Kaiser
0
Passing a struct as a pointer avoids making another copy:
func main() {
char := character{name: "Helmut Van Kaiser", class: "Fighter", level: 4}
fmt.Println(char.name)
fmt.Println(char.class)
fmt.Println(char.level)
levelUp(&char)
fmt.Println(char.level)
}
func levelUp(char *character) {
char.level++
}
prints
Helmut Van Kaiser
Fighter
4
5
methods
Structs can have methods that have access to struct properties:
type character struct {
name string
class string
level int
}
func (char *character) levelUp() {
char.level++
}
func main() {
char := character{name: "Helmut Van Kaiser", class: "Fighter", level: 4}
fmt.Println(char.name)
fmt.Println(char.class)
fmt.Println(char.level)
char.levelUp()
fmt.Println(char.level)
}
prints
Helmut Van Kaiser
Fighter
4
5
pointer vs value receiver
You can pass your struct by value or by reference to your method. If your method needs to modify the struct in some way, it must be passed as a pointer.
type character struct {
name string
class string
level int
}
func (char *character) levelUp() { // pointer receiver
char.level++
}
func (char character) card() string { // value receiver
return fmt.Sprintf("Player %v is a level %v %v", char.name, char.level, char.class)
}
func main() {
char := character{name: "Helmut Van Kaiser", class: "Fighter", level: 4}
fmt.Println(char.card())
char.levelUp()
fmt.Println(char.card())
}
Note
By convention, receiver names should be related to the struct in some way. A receiver for character
struct might be c
or char
but never self
or this
. All methods for the same struct should share the same receiver name, regardless of if they are pointer or value receivers.
The compiler does not catch cases where your method modifies your struct passed by value:
type character struct {
name string
class string
level int
}
func (char character) levelUp() { // value receiver
char.level++ // level++ modifies the struct copy
}
func main() {
char := character{name: "Helmut Van Kaiser", class: "Fighter", level: 4}
fmt.Println(char.name)
fmt.Println(char.class)
fmt.Println(char.level)
char.levelUp()
fmt.Println(char.level)
}
prints
Helmut Van Kaiser
Fighter
4
4
Note
There are linters such as revive available to help catch cases like this, which can be tricky to notice manually.
Hands on!
- In the repo, open the file
./intermediate/09structs.go
- Complete the TODOs
- Run
make 09
from project root (alternatively, typego run ./09structs.go
) - Example implementation available on
solutions
branch