Struct literals
Declaring a variable with var b Book gives you a zero-valued struct and leaves you to fill the fields in afterwards. Struct literals build and populate in one expression, which is the shape you will see in real Go code almost everywhere.
Named-field literals (the idiomatic form)
Name each field explicitly, any order, any subset:
b := Book{
Title: "The Go Programming Language",
Author: "Alan A. A. Donovan",
Pages: 380,
}
A reader who sees this literal knows exactly which value is which without looking up the type definition. You do not have to list every field, either: anything you leave out gets the zero value of its type:
draft := Book{Title: "Work in progress"}
fmt.Println(draft) // {Work in progress 0}
Author defaulted to "" and Pages defaulted to 0.
Positional literals (shorter, more fragile)
There is also a positional form that lists values in declaration order, with no field names:
b := Book{"The Go Programming Language", "Alan A. A. Donovan", 380}
It is shorter, and it is fragile. If someone adds a new field between Author and Pages, every positional literal in the codebase silently assigns the old values to the wrong fields. Depending on types, the compiler may or may not catch the mistake. The named form keeps working regardless.
Use the named form Book{Title: "...", Author: "..."} for any struct with three or more fields, or any struct that is likely to grow. The positional form is fine for tiny types where the ordering is obvious and stable (Point{1, 2}, Size{80, 24}). For anything else, name every field and let the code read itself.
Literals in expressions
Struct literals can appear anywhere a value of the struct type is expected, not just on the right-hand side of an assignment:
func describe(b Book) {
fmt.Println(b.Title, "by", b.Author)
}
func main() {
describe(Book{
Title: "The Go Programming Language",
Author: "Alan A. A. Donovan",
})
}
That is how most "constructor-like" code in Go looks. No new, no factory function by default, just a literal passed straight into whatever wants one. When a struct's construction genuinely needs logic (validation, normalisation, default fill-in), it is usual to wrap the literal in a NewThing function that returns the struct; until then, the literal is the constructor.
Nested structs
A struct field can be another struct type. You nest literals the same way you nest field accesses:
type Address struct {
City string
Country string
}
type Person struct {
Name string
Age int
Address Address
}
alice := Person{
Name: "Alice",
Age: 30,
Address: Address{
City: "Berlin",
Country: "Germany",
},
}
fmt.Println(alice.Address.City) // Berlin
alice.Address.City reaches through two levels of struct to get the string. Writing is the same shape: alice.Address.City = "Munich" updates the nested field directly. There is no depth limit, but in practice you rarely see more than two or three levels; deeper nesting usually signals a missing abstraction worth pulling out into its own type.
Recursive structs
A struct's field can refer to the same struct type, as long as the reference goes through a pointer. This is how Go expresses trees, linked lists, and any data structure where each node knows about other nodes of the same kind:
type TreeNode struct {
Value int
Left, Right *TreeNode
}
root := &TreeNode{
Value: 10,
Left: &TreeNode{Value: 5},
Right: &TreeNode{Value: 15},
}
fmt.Println(root.Left.Value) // 5
fmt.Println(root.Right.Value) // 15
TreeNode contains two *TreeNode fields. The compiler accepts this because a pointer has a fixed size (one machine word) regardless of what it points at, so the total size of a TreeNode is known at compile time.
The non-pointer version would not compile:
type TreeNode struct {
Value int
Left, Right TreeNode // compile error: invalid recursive type
}
"A TreeNode contains two TreeNode values" has no fixed size: each of those inner nodes would in turn contain two more, and so on forever. The compiler cannot lay that out in memory. Pointers break the recursion because the node itself always fits in a known footprint, and the children live separately in memory, reached through the pointer.
The same pattern builds linked lists (type Node struct { Value int; Next *Node }), ASTs (type Expr struct { Op string; Left, Right *Expr }), and any other graph-shaped data. Whenever a struct needs to reference another of the same type, the answer is "through a pointer".
The starter defines Book and Library:
type Book struct {
Title string
Author string
Pages int
}
type Library struct {
Name string
Featured Book
}
In main:
- Build a
Libraryusing a named literal.Nameis"City Library".Featuredis a nested named-literalBookwith title"Go in Practice", author"Matt Butcher", and200pages. - Print the library on its own line.
- Print only the featured book's title with
lib.Featured.Title.
Expected output:
{City Library {Go in Practice Matt Butcher 200}}
Go in Practice
{City Library {Go in Practice Matt Butcher 200}}
Go in Practice