Create a slice of structs by declaring it with make or an empty literal, then append initialized struct instances to it. You can also initialize the entire slice in a single declaration if you know the elements upfront.
Here is the most common pattern using make and append for dynamic growth:
type User struct {
Name string
Age int
}
// Create an empty slice with capacity for 2 elements
users := make([]User, 0, 2)
// Append new struct instances
users = append(users, User{Name: "Alice", Age: 30})
users = append(users, User{Name: "Bob", Age: 25})
// Access elements
fmt.Println(users[0].Name) // Output: Alice
If you know all elements at compile time, use a composite literal for a more concise definition:
type Product struct {
ID int
Name string
}
// Initialize slice with predefined elements
products := []Product{
{ID: 1, Name: "Laptop"},
{ID: 2, Name: "Mouse"},
}
// Iterate over the slice
for _, p := range products {
fmt.Printf("ID: %d, Name: %s\n", p.ID, p.Name)
}
Key distinctions to remember: make([]User, 0, 2) creates a slice with zero length but a capacity of 2, allowing efficient appends without immediate reallocation. Using []User{} creates an empty slice with zero length and zero capacity. When appending, always ensure the struct fields are initialized correctly; Go zero-values (empty strings, zero integers) apply automatically if you omit fields, but explicit initialization prevents ambiguity. Avoid creating a slice of pointers ([]*User) unless you specifically need to modify the underlying struct instances after appending or share them across functions, as passing structs by value copies the entire data structure.