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
⚠️ Note: Structs are value types; passing them as arguments creates a copy. To modify the original value, pass a pointer *S.


2. Basic Syntax/Usage

Defining a Struct

GO
// Define a Person struct
type Person struct {
    Name string
    Age  int
    City string
}
💡 Tip: In Go, if a struct field name starts with an uppercase letter, it is exported (public); if it starts with a lowercase letter, it is unexported (private) and only visible within the package.

Initialization Methods

GO
// 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"}
💡 Tip: A struct declared with var p Person initializes all fields to the zero value of their respective types (string → "", int → 0, bool → false).

Accessing and Modifying Fields

GO
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
💡 Tip: The syntax for accessing fields through pointers and values is identical — Go automatically dereferences, so there's no need for p->Name.

Struct Pointers

GO
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
💡 Tip: Go has no -> 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.

GO
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)
}
▶ Try it Yourself

Output:

TEXT
{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.

GO
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)
}
▶ Try it Yourself

Output:

TEXT
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.

GO
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)
    }
}
▶ Try it Yourself

Output:

TEXT
=== 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.

GO
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:

TEXT
{
  "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).

GO
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:

TEXT
[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:

GO
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:

GO
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:

GO
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:

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:

GO
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


📝 Exercises

Exercise 1 (⭐)

Define a Rectangle struct with Width and Height fields. Implement the following:

  1. Create a Rectangle instance and print it
  2. Write an Area() method to calculate the area
  3. Write a Perimeter() method to calculate the perimeter
  4. 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:

  1. Deposit(amount) for deposits
  2. Withdraw(amount) for withdrawals (notify when balance is insufficient)
  3. Transfer(other *BankAccount, amount) for transfers
  4. Use JSON tags to serialize account info (hide balance using json:"-")

Exercise 3 (⭐⭐⭐)

Implement a product inventory management system:

  1. Define a Product struct (ID, Name, Price, Stock, Tags)
  2. Define an Inventory struct (ShopName, Products slice)
  3. Implement methods: AddProduct, RemoveProduct(id), FindByTag(tag), TotalValue() (total inventory value)
  4. Implement JSON serialization with requirements: Price to two decimal places, omit Stock when 0
  5. Create instances, add products, search by tag, calculate total value, and output as JSON

Next Lesson

👉 08-methods - Methods

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏