Structs as data
The fourth job PHP's array does is carrying records around: ['dest' => 'Epping', 'platform' => 3]. Modern PHP often upgrades that to a readonly class with promoted constructor properties. Go's version is the struct, which you met briefly in 1.4. This lesson finishes the job.
A fixed, checked shape
type Departure struct { Dest string Platform int }
A struct type lists its fields and their types, and that list is final. Read or write a field that isn't there and it's a compile error, not a quiet new key:
| PHP | $dep['platfrom'] = 4; |
|---|---|
| PHP, quietly | a brand-new key called "platfrom" |
| Go | dep.Platfrom undefined |
(The capital letters are 1.11's visibility rule. Dest and Platform can be used from other packages, and lower-case fields couldn't.)
Literals
dep := Departure{Dest: "Epping", Platform: 3} later := Departure{Dest: "Ealing"} // Platform is 0 var blank Departure // every field at zero
A keyed literal names each field, a lot like PHP's named arguments. Any field you leave out gets its zero value (1.4), and that's the only kind of "default" a struct has. There's no constructor here: Go code that needs validation or real defaults writes a NewDeparture function, and Unit 3 covers that.
You can also leave the names out, Departure{"Epping", 3}, as long as you give every field in order. Prefer keyed literals: an unkeyed one breaks, or worse, silently swaps two fields, as soon as someone adds or reorders a field. go vet complains about unkeyed literals of structs from other packages for exactly that reason.
Structs can hold other structs, slices and maps, and slices and maps can hold structs. A []Departure is a departure board, and a map[string]Departure looks one up by destination.
Structs are values
Assigning a struct copies every field, just like an array in 2.1:
a := Departure{Dest: "Epping", Platform: 3} b := a b.Platform = 4 // a.Platform is still 3
That's the opposite of a PHP object, where $b = $a gives you the same object. Passing a struct to a function copies it too. (When you do want to share one, that's a pointer, lesson 3.3.) Two structs can be compared with == when all their fields can, and such a struct can even be a map key.
One catch: copying a struct copies its fields, and a slice field is only a header (2.3). Copy a struct with a Stops []string field and both copies share the same stops.
Structs inside slices and maps
Two rules follow from "structs are values", and both trip people up.
range gives you a copy of each struct. You saw this in 1.9 with strings, and it bites harder with structs, because changing a field looks so reasonable:
for _, t := range board { t.Delay += 5 // changes the copy, then throws it away } for i := range board { board[i].Delay += 5 // changes the board }
You can't change a field of a struct inside a map.
Mind the gap
byDest["Epping"].Platform = 4 doesn't compile. You get cannot assign to struct field byDest["Epping"].Platform in map. A map lookup hands you a copy, so changing one field of it would be lost, and Go refuses rather than let you think it worked. Copy the struct out, change it, and put it back: t := byDest["Epping"], then t.Platform = 4, then byDest["Epping"] = t.
Anonymous structs
Sometimes a shape is only needed in one place. An anonymous struct defines the type right where it's used:
row := struct { Station string Taps int }{"Bank", 950}
You've been reading these all course. Every table-driven test since 0.6 starts with tests := []struct{ … }{ … }: a slice of an anonymous struct, one per test case. They're also handy for one-off JSON shapes (7.3).
Laravel corner
An Eloquent model's attributes go through magic __get, so $departure->platfrom is just null, with no warning. Packages like spatie/laravel-data exist to give Laravel the typed, checked DTOs that a Go struct gives you by default.
Recap
- A struct is a fixed set of typed fields. Unknown fields are compile errors.
- Use keyed literals. Missing fields get zero values, and there are no other defaults.
- Structs are values: assigning or passing copies them, unlike PHP objects. Slice fields inside still share.
rangegives copies, so change structs in a slice withxs[i].Field.- You can't assign to a field of a struct in a map: copy it out, change it, put it back.
- Anonymous structs,
struct{ … }{ … }, are for one-off shapes like test tables.