Structs
Structs
Imagine you're filling out a personal information form: name, age, phone number, address — these pieces of information are related to each other and together describe a person. In Go, a struct is that "form": it combines multiple fields of different types into a custom composite type. Compared to maps, struct fields are determined at compile time, making them type-safe and semantically clear. Structs are the core way to organize data in Go.
1. Core Concepts
| Concept | Description |
|---|---|
| Definition | type StructName struct { ... }, field name comes before the type |
| Initialization | Literal S{f1: v1, f2: v2}, new(S) returns a pointer, &S{} |
| Field Access | Dot notation s.Field, pointers also use dot notation p.Field (no -> needed) |
| Value Type | Structs are value types; assignment and function arguments copy the entire struct |
| Pointer Receiver | Obtain a pointer via &s; modifying the struct through the pointer affects the original value |
| Anonymous Fields | Write only the type without a field name; the type name becomes the field name, enabling "composition" |
| Nested Structs | Struct fields can themselves be structs, supporting multi-level nesting |
| Struct Tags | Metadata wrapped in backticks, commonly used for JSON/database mapping |
*S.
2. Basic Syntax/Usage
Defining a Struct
// Define a Person struct
type Person struct {
Name string
Age int
City string
}
Initialization Methods
// Method 1: field name: value (recommended, good readability, order doesn't matter)
p1 := Person{Name: "Alice", Age: 25, City: "Beijing"}
// Method 2: assign by order (not recommended for many fields, error-prone)
p2 := Person{"Bob", 30, "Shanghai"}
// Method 3: declare first, then assign
var p3 Person
p3.Name = "Charlie"
p3.Age = 28
p3.City = "Guangzhou"
// Method 4: create with new (returns pointer)
p4 := new(Person) // *Person
p4.Name = "Diana"
// Method 5: take address (returns pointer)
p5 := &Person{Name: "Eve", Age: 22, City: "Shenzhen"}
var p Person initializes all fields to the zero value of their respective types (string → "", int → 0, bool → false).
Accessing and Modifying Fields
p := Person{Name: "Alice", Age: 25}
// Read a field
fmt.Println(p.Name) // Alice
// Modify a field
p.Age = 26
fmt.Println(p.Age) // 26
p->Name.
Struct Pointers
p := &Person{Name: "Alice", Age: 25}
// Go auto-dereferences, use dot notation directly
fmt.Println(p.Name) // Alice
// Modify the original value through the pointer
p.Age = 30
-> operator. Whether p is a value or a pointer, you always use p.Field to access fields.
3. Example Code
Example 1: Basic Usage (Difficulty ⭐)
Define a Book struct, create instances, and print information.
package main
import "fmt"
// Book defines a book struct
type Book struct {
Title string
Author string
Pages int
Price float64
}
func main() {
// Create using a literal
book1 := Book{
Title: "The Go Programming Language",
Author: "Alan Donovan",
Pages: 350,
Price: 59.9,
}
// Print the struct
fmt.Println(book1)
// Access individual fields
fmt.Printf("Title: %s\n", book1.Title)
fmt.Printf("Author: %s\n", book1.Author)
fmt.Printf("Price: %.1f\n", book1.Price)
// Modify a field
book1.Price = 49.9
fmt.Printf("New price: %.1f\n", book1.Price)
// Create by order (not recommended)
book2 := Book{"Python Crash Course", "Eric Matthes", 280, 45.0}
fmt.Println(book2)
}
Output:
{The Go Programming Language Alan Donovan 350 59.9}
Title: The Go Programming Language
Author: Alan Donovan
Price: 59.9
New price: 49.9
{Python Crash Course Eric Matthes 280 45}
Example 2: Advanced Usage (Difficulty ⭐⭐)
Using pointers, nested structs, and anonymous fields.
package main
import "fmt"
// Address struct
type Address struct {
City string
Street string
ZipCode string
}
// Employee struct (uses anonymous field Address)
type Employee struct {
Name string
Age int
Address // Anonymous field: type name is the field name
Salary float64
}
func main() {
// ========== Nested struct initialization ==========
emp := Employee{
Name: "Alice",
Age: 30,
Salary: 15000,
Address: Address{
City: "Beijing",
Street: "1 Zhongguancun Street",
ZipCode: "100080",
},
}
fmt.Printf("Employee: %s\n", emp.Name)
fmt.Printf("City: %s\n", emp.Address.City) // Explicit access
// ========== Anonymous field "promotion" access ==========
// Anonymous fields can be accessed directly by type name
fmt.Printf("Street: %s\n", emp.Street) // Same as emp.Address.Street
fmt.Printf("ZipCode: %s\n", emp.ZipCode) // Same as emp.Address.ZipCode
// ========== Pointer operations ==========
empPtr := &emp
// Modify through pointer (Go auto-dereferences)
empPtr.Age = 31
empPtr.Salary = 18000
empPtr.City = "Shanghai" // Modify anonymous field through pointer
fmt.Printf("\nAfter modification: %+v\n", *empPtr)
// ========== Value type vs Pointer type ==========
fmt.Println("\n--- Value type vs Pointer type ---")
// Value type: assignment is copying
emp2 := emp
emp2.Name = "Bob"
fmt.Printf("emp.Name: %s\n", emp.Name) // Still Alice
fmt.Printf("emp2.Name: %s\n", emp2.Name) // Bob
// Pointer type: assignment is sharing
emp3 := empPtr
emp3.Name = "Charlie"
fmt.Printf("empPtr.Name: %s\n", empPtr.Name) // Charlie (modified)
}
Output:
Employee: Alice
City: Beijing
Street: 1 Zhongguancun Street
ZipCode: 100080
After modification: {Name:Alice Age:31 Address:{City:Shanghai Street:1 Zhongguancun Street ZipCode:100080} Salary:18000}
--- Value type vs Pointer type ---
emp.Name: Alice
emp2.Name: Bob
empPtr.Name: Charlie
Example 3: Comprehensive Application (Difficulty ⭐⭐⭐)
Using struct tags for JSON serialization/deserialization, and building a student management system.
package main
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
// Student struct with JSON tags
type Student struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
Score float64 `json:"score"`
Tags []string `json:"tags,omitempty"` // omitempty: omit when empty
secret string // Unexported field, not accessible via JSON
}
// ClassRoom (nested struct)
type ClassRoom struct {
ClassName string `json:"class_name"`
Students []Student `json:"students"`
}
// AddStudent adds a student
func (c *ClassRoom) AddStudent(s Student) {
c.Students = append(c.Students, s)
}
// GetTopN gets the top N students by score
func (c *ClassRoom) GetTopN(n int) []Student {
// Make a copy to avoid modifying the original data
sorted := make([]Student, len(c.Students))
copy(sorted, c.Students)
// Sort by score in descending order
sort.Slice(sorted, func(i, j int) bool {
return sorted[i].Score > sorted[j].Score
})
if n > len(sorted) {
n = len(sorted)
}
return sorted[:n]
}
// Average calculates the average score
func (c *ClassRoom) Average() float64 {
if len(c.Students) == 0 {
return 0
}
total := 0.0
for _, s := range c.Students {
total += s.Score
}
return total / float64(len(c.Students))
}
// SearchByName searches by name (fuzzy match)
func (c *ClassRoom) SearchByName(keyword string) []Student {
var result []Student
for _, s := range c.Students {
if strings.Contains(s.Name, keyword) {
result = append(result, s)
}
}
return result
}
func main() {
// Create a classroom
class := &ClassRoom{ClassName: "Class 1, Grade 2024"}
// Add students
students := []Student{
{ID: 1, Name: "Zhang San", Age: 18, Score: 92.5, Tags: []string{"good at math", "class monitor"}},
{ID: 2, Name: "Li Si", Age: 19, Score: 88.0, Tags: []string{"good at sports"}},
{ID: 3, Name: "Wang Wu", Age: 18, Score: 95.5},
{ID: 4, Name: "Zhao Liu", Age: 20, Score: 78.0, Tags: []string{"art talent"}},
{ID: 5, Name: "Zhang Wei", Age: 19, Score: 91.0, Tags: []string{"good at math", "competition winner"}},
}
for _, s := range students {
class.AddStudent(s)
}
// ========== JSON Serialization ==========
fmt.Println("=== JSON Serialization ===")
jsonData, err := json.MarshalIndent(class, "", " ")
if err != nil {
fmt.Println("Serialization failed:", err)
} else {
fmt.Println(string(jsonData))
}
// ========== JSON Deserialization ==========
fmt.Println("\n=== JSON Deserialization ===")
jsonStr := `{"id":6,"name":"Sun Qi","age":17,"score":97.0,"tags":["good at English"]}`
var newStudent Student
if err := json.Unmarshal([]byte(jsonStr), &newStudent); err != nil {
fmt.Println("Deserialization failed:", err)
} else {
fmt.Printf("New student: %+v\n", newStudent)
}
// ========== Feature Demo ==========
fmt.Println("\n=== Class Info ===")
fmt.Printf("Class: %s, total %d students\n", class.ClassName, len(class.Students))
fmt.Printf("Average score: %.1f\n", class.Average())
fmt.Println("\n=== Top 3 Scores ===")
top3 := class.GetTopN(3)
for i, s := range top3 {
fmt.Printf(" #%d: %s (%.1f)\n", i+1, s.Name, s.Score)
}
fmt.Println("\n=== Search 'Zhang' ===")
results := class.SearchByName("Zhang")
for _, s := range results {
fmt.Printf(" %s (ID: %d, Score: %.1f)\n", s.Name, s.ID, s.Score)
}
}
Output:
=== JSON Serialization ===
{
"class_name": "Class 1, Grade 2024",
"students": [
{
"id": 1,
"name": "Zhang San",
"age": 18,
"score": 92.5,
"tags": [
"good at math",
"class monitor"
]
},
{
"id": 2,
"name": "Li Si",
"age": 19,
"score": 88
},
{
"id": 3,
"name": "Wang Wu",
"age": 18,
"score": 95.5
},
{
"id": 4,
"name": "Zhao Liu",
"age": 20,
"score": 78,
"tags": [
"art talent"
]
},
{
"id": 5,
"name": "Zhang Wei",
"age": 19,
"score": 91,
"tags": [
"good at math",
"competition winner"
]
}
]
}
=== JSON Deserialization ===
New student: {ID:6 Name:Sun Qi Age:17 Score:97 Tags:[good at English]}
=== Class Info ===
Class: Class 1, Grade 2024, total 5 students
Average score: 89.0
=== Top 3 Scores ===
#1: Wang Wu (95.5)
#2: Zhang San (92.5)
#3: Zhang Wei (91.0)
=== Search 'Zhang' ===
Zhang San (ID: 1, Score: 92.5)
Zhang Wei (ID: 5, Score: 91.0)
3. Common Application Scenarios
Scenario 1: JSON Data Interaction
In web development, structs are the standard way to handle JSON requests/responses. Tags control JSON field names.
package main
import (
"encoding/json"
"fmt"
)
// APIResponse unified API response structure
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
// UserDTO user data transfer object
type UserDTO struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"-"` // "-" means ignore this field in JSON
}
func main() {
// Construct a response
user := UserDTO{
ID: 1,
Username: "alice",
Email: "alice@example.com",
Password: "secret123", // Won't be serialized
}
resp := APIResponse{
Code: 200,
Message: "success",
Data: user,
}
// Serialize
jsonBytes, _ := json.MarshalIndent(resp, "", " ")
fmt.Println(string(jsonBytes))
// Deserialize
jsonStr := `{"code":404,"message":"User not found"}`
var errResp APIResponse
json.Unmarshal([]byte(jsonStr), &errResp)
fmt.Printf("\nError code: %d, Message: %s\n", errResp.Code, errResp.Message)
}
Output:
{
"code": 200,
"message": "success",
"data": {
"id": 1,
"username": "alice",
"email": "alice@example.com"
}
}
Error code: 404, Message: User not found
Scenario 2: Object-Oriented Simulation (Composition Over Inheritance)
Go has no classes or inheritance; code reuse is achieved through struct nesting (composition).
package main
import "fmt"
// Logger logging capability
type Logger struct {
Prefix string
}
// Log outputs a log message
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.Prefix, msg)
}
// Cache caching capability
type Cache struct {
data map[string]string
}
// Get retrieves from cache
func (c *Cache) Get(key string) (string, bool) {
val, ok := c.data[key]
return val, ok
}
// Set sets a cache entry
func (c *Cache) Set(key, value string) {
if c.data == nil {
c.data = make(map[string]string)
}
c.data[key] = value
}
// UserService reuses Logger and Cache capabilities through composition
type UserService struct {
Logger // Embed Logger
Cache // Embed Cache
Name string
}
func main() {
svc := UserService{
Logger: Logger{Prefix: "UserService"},
Name: "User Service",
}
// Directly call embedded methods
svc.Log("Service started") // From Logger
svc.Set("user:1", "Alice") // From Cache
svc.Log("Cache initialized")
if val, ok := svc.Get("user:1"); ok {
svc.Log(fmt.Sprintf("Found user: %s", val))
}
}
Output:
[UserService] Service started
[UserService] Cache initialized
[UserService] Found user: Alice
❓ FAQ
Q1: Is a struct a value type or a reference type?
Structs are value types. Assignment and function arguments copy the entire struct. To modify the original value, pass a pointer:
func updateAge(p *Person, age int) {
p.Age = age // Directly modifies the original value
}
p := Person{Name: "Alice", Age: 25}
updateAge(&p, 30)
fmt.Println(p.Age) // 30
Q2: What are anonymous fields for? How are they different from nested structs?
Anonymous fields implement "composition." The inner type's methods and fields are "promoted" to the outer type:
type Address struct {
City string
}
type Person struct {
Name string
Address // Anonymous field
}
p := Person{Name: "Alice", Address: Address{City: "Beijing"}}
fmt.Println(p.City) // Direct access, same as p.Address.City
fmt.Println(p.Address.City) // Also works with explicit access
If nested structs have fields with the same name, you need to use the full path to disambiguate.
Q3: What are struct tags? How do I use them?
Tags are metadata wrapped in backticks, commonly used to control the behavior of JSON, database ORM, and other libraries:
type User struct {
ID int `json:"id" db:"user_id"`
Name string `json:"name" db:"user_name"`
Password string `json:"-" db:"password"` // Ignored in JSON
Email string `json:"email,omitempty" db:"email"` // Omitted in JSON when empty
}
Common tags:
json:"fieldName"— Controls the JSON field namejson:"-"— Ignores the field during serializationjson:",omitempty"— Omits when zero valuedb:"column_name"— Database field mapping (requires ORM support)
Q4: Can structs be compared?
It depends on the field types. If all fields are comparable types (int, string, bool, arrays, etc.), structs can be compared with == and !=. If they contain incomparable fields like slices, maps, or funcs, they cannot be directly compared:
type Point struct { X, Y int }
p1 := Point{1, 2}
p2 := Point{1, 2}
fmt.Println(p1 == p2) // true
// Structs containing slices cannot use ==
type Data struct { Items []int }
// d1 == d2 // Compile error!
📖 Summary
- struct is Go's custom composite type, combining multiple fields into a single unit
- Structs are value types; assignment and function arguments copy the entire struct; pass a pointer
*Sto modify the original - Field access uses dot notation
s.Field; pointers also usep.Field, no->needed - Anonymous fields (type only, no field name) implement composition; inner fields are "promoted" to the outer level
- Nested structs support multi-level data organization
- Struct tags are defined with backticks, commonly used for JSON/ORM field mapping
json:"-"ignores a field;json:",omitempty"omits zero values- Whether a struct is comparable depends on its field types
📝 Exercises
Exercise 1 (⭐)
Define a Rectangle struct with Width and Height fields. Implement the following:
- Create a Rectangle instance and print it
- Write an
Area()method to calculate the area - Write a
Perimeter()method to calculate the perimeter - Write an
IsSquare()method to check if it's a square
Exercise 2 (⭐⭐)
Define a BankAccount struct with Owner (string) and Balance (float64) fields. Implement the following:
Deposit(amount)for depositsWithdraw(amount)for withdrawals (notify when balance is insufficient)Transfer(other *BankAccount, amount)for transfers- Use JSON tags to serialize account info (hide balance using
json:"-")
Exercise 3 (⭐⭐⭐)
Implement a product inventory management system:
- Define a
Productstruct (ID, Name, Price, Stock, Tags) - Define an
Inventorystruct (ShopName, Products slice) - Implement methods:
AddProduct,RemoveProduct(id),FindByTag(tag),TotalValue()(total inventory value) - Implement JSON serialization with requirements: Price to two decimal places, omit Stock when 0
- Create instances, add products, search by tag, calculate total value, and output as JSON



