Declaring Structs
A struct groups named fields under one type name; build values with or without field names, or let every field start at zero.
type Book struct { Title string; Pages int }A struct is one value made of several named parts. A book has a title, a page count and a stock level. Keeping those three in three separate variables means passing three things everywhere and keeping them in step by hand. A struct keeps them together as one value with one type name.
Declaring a struct type
You declare the type once, then make as many values of it as you like. Each named part is a field. A field has a name and a type. A field name that starts with a capital letter can be used from other packages; a lowercase one can only be used inside the package that declared it.
Three ways to build a value
A composite literal builds a value by listing its contents in braces. With field names, as in Book{Title: "Dune", Pages: 412}, the order does not matter and you may leave fields out. Anything left out gets its zero value. Without field names, as in Book{"Dune", 412, 2}, you must give every field, in declaration order. Declaring a variable with no literal at all, var b Book, gives a struct whose every field holds its zero value: 0 for numbers, "" for strings, false for booleans.
Reading and writing fields
A dot reaches a field. b.Title reads it and b.Title = "Go" writes it.
What trips people up
The form without field names is tied to field order. Add a field, or swap two of the same type, and old literals either stop compiling or quietly put values in the wrong fields. Use field names everywhere except very small types such as a two-number point.
Example
package main
import "fmt"
// Book is a struct type: one name for a group of related values.
type Book struct {
Title string
Pages int
Stock int
}
func main() {
// Field names given, so the order does not matter.
a := Book{Title: "Go in Practice", Pages: 320, Stock: 4}
// No field names: every field must be listed, in declaration order.
b := Book{"Dune", 412, 2}
// No literal at all: each field holds its type's zero value.
var c Book
fmt.Println(a.Title, a.Pages, a.Stock)
fmt.Println(b.Title, b.Pages, b.Stock)
// %q puts quotes round a string, so an empty one is visible.
fmt.Printf("%q %d %d\n", c.Title, c.Pages, c.Stock)
}
// Output:
// Go in Practice 320 4
// Dune 412 2
// "" 0 0When to use it
- Model a database row or an API payload as one named type instead of loose variables.
- Return several related values from a function as a single value.
- Build a list of records, such as a cart or a result page, as a slice of structs.
- Start a value with every field at its zero value and fill fields in as they are known.
More examples
A struct inside a struct
A field's type can be another struct. Reaching a nested field takes one dot per level, and a nested field can be assigned directly. This is the shape of most real records: an order that holds a shipping address, a user that holds a set of preferences.
package main
import "fmt"
type Address struct {
City string
Code string
}
type Customer struct {
Name string
Home Address
}
func main() {
c := Customer{
Name: "Ada",
Home: Address{City: "Leeds", Code: "LS1"},
}
// One dot per level: c, then Home, then City.
fmt.Println(c.Name, c.Home.City)
c.Home.Code = "LS2"
fmt.Println(c.Home.Code)
}
// Output:
// Ada Leeds
// LS2A slice of structs, with the type name left out
A slice of structs is how a list of records is held in memory: a shopping cart, a page of search results, the rows read back from a database. The shorthand that drops the repeated element type is what you will see in real code, so recognise both forms as the same thing.
package main
import "fmt"
type Item struct {
Name string
Cost int
}
func main() {
// Longhand: each element names its type.
long := []Item{Item{Name: "pen", Cost: 2}, Item{Name: "pad", Cost: 5}}
// Shorthand: inside a []Item literal the element type is already known,
// so Item can be dropped. These two slices are identical.
short := []Item{
{Name: "pen", Cost: 2},
{Name: "pad", Cost: 5},
}
total := 0
for _, it := range short {
total += it.Cost
}
fmt.Println(len(long), len(short), total)
}
// Output:
// 2 2 7An anonymous struct
When a grouping is needed in exactly one place, the type can be written where the value is built instead of being declared at package level. Reach for it for a one-off bundle of values, and declare a named type as soon as a second piece of code needs to mention it.
package main
import "fmt"
func main() {
// A struct type written in place, with no name of its own.
page := struct {
Title string
Views int
}{Title: "Home", Views: 12}
fmt.Println(page.Title, page.Views)
}
// Output:
// Home 12
Discussion