Skip to content

🏗️ Structs, Methods & Memory Layout

Structs là data container cơ bản nhất trong Go. Nhưng hiểu cách chúng được layout trong memory sẽ giúp bạn viết code hiệu quả hơn đáng kể.

🧠 Struct Internals: Memory Alignment

The Padding Problem

🎓 Professor Tom's Deep Dive: CPU Memory Access

CPU không đọc memory byte-by-byte. Nó đọc theo word size (8 bytes trên 64-bit system).

Alignment Rule: Mỗi field phải được aligned theo bội số của size của nó:

  • int64 (8 bytes) → phải bắt đầu ở địa chỉ chia hết cho 8
  • int32 (4 bytes) → phải bắt đầu ở địa chỉ chia hết cho 4
  • bool (1 byte) → có thể bắt đầu ở bất kỳ đâu

Nếu không aligned? CPU phải thực hiện 2 memory reads + bitshift → performance hit.

Bad vs Good Struct Layout

go
// ❌ BAD: 24 bytes (với 7 bytes padding bị waste!)
type BadLayout struct {
    Active  bool    // 1 byte
    // ─────────── 7 bytes padding ──────────
    ID      int64   // 8 bytes (phải aligned 8)
    Enabled bool    // 1 byte
    // ─────────── 7 bytes padding ──────────
}

// ✅ GOOD: 16 bytes (chỉ 6 bytes padding)
type GoodLayout struct {
    ID      int64   // 8 bytes
    Active  bool    // 1 byte
    Enabled bool    // 1 byte
    // ─────────── 6 bytes padding ──────────
}

Memory Visualization

┌─────────────────────────────────────────────────────────────────────┐
│                    BadLayout (24 bytes total)                       │
├─────────────────────────────────────────────────────────────────────┤
│ Offset │  0  │  1  │  2  │  3  │  4  │  5  │  6  │  7  │           │
│        │ Act │ PAD │ PAD │ PAD │ PAD │ PAD │ PAD │ PAD │           │
├────────┼─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┤           │
│ Offset │  8  │  9  │ 10  │ 11  │ 12  │ 13  │ 14  │ 15  │           │
│        │ ◄───────────── ID (int64) ──────────────────► │           │
├────────┼─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┤           │
│ Offset │ 16  │ 17  │ 18  │ 19  │ 20  │ 21  │ 22  │ 23  │           │
│        │ Enb │ PAD │ PAD │ PAD │ PAD │ PAD │ PAD │ PAD │           │
└────────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴───────────┘
                  ▲ 7 bytes wasted        ▲ 7 bytes wasted

┌─────────────────────────────────────────────────────────────────────┐
│                   GoodLayout (16 bytes total)                       │
├─────────────────────────────────────────────────────────────────────┤
│ Offset │  0  │  1  │  2  │  3  │  4  │  5  │  6  │  7  │           │
│        │ ◄───────────── ID (int64) ──────────────────► │           │
├────────┼─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┤           │
│ Offset │  8  │  9  │ 10  │ 11  │ 12  │ 13  │ 14  │ 15  │           │
│        │ Act │ Enb │ PAD │ PAD │ PAD │ PAD │ PAD │ PAD │           │
└────────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴───────────┘
                        ▲ Only 6 bytes padding (struct alignment)

Checking Struct Size

go
import "unsafe"

fmt.Println(unsafe.Sizeof(BadLayout{}))   // 24
fmt.Println(unsafe.Sizeof(GoodLayout{}))  // 16

// Với 1 triệu records:
// BadLayout:  24MB
// GoodLayout: 16MB → 33% less memory!

📌 HPN Standard: Field Ordering

Quy tắc sắp xếp fields:

  1. Largest first - Xếp fields lớn nhất trước (int64, pointers)
  2. Same-size group - Nhóm các fields cùng size
  3. Smallest last - bool, byte xếp cuối cùng
go
// ✅ Production-ready struct
type User struct {
    ID        int64     // 8 bytes
    CreatedAt time.Time // 24 bytes (struct)
    Name      string    // 16 bytes (ptr + len)
    Email     string    // 16 bytes
    Age       int32     // 4 bytes
    IsActive  bool      // 1 byte
    IsAdmin   bool      // 1 byte
    // Total: ≈72 bytes (optimized)
}

🔗 Composition over Inheritance

Go KHÔNG có Inheritance

🔥 Raizo's Warning: Mental Model Shift

Nếu bạn từ Java/C++ đến, hãy quên inheritance đi. Go dùng composition.

java
// Java - Inheritance ("is-a")
class Dog extends Animal {
    @Override void speak() { ... }
}
go
// Go - Composition ("has-a" + interface)
type Dog struct {
    animal Animal  // Embedded hoặc field
}

func (d Dog) Speak() string { return "Woof!" }

Embedded Fields & Field Promotion

go
type Address struct {
    Street  string
    City    string
    Country string
}

type Person struct {
    Name    string
    Age     int
    Address        // Embedded (không có field name)
}

func main() {
    p := Person{
        Name: "Raizo",
        Age:  30,
        Address: Address{
            Street:  "123 Go Street",
            City:    "Saigon",
            Country: "Vietnam",
        },
    }
    
    // Field Promotion - truy cập trực tiếp
    fmt.Println(p.City)      // "Saigon" ← Promoted từ Address
    fmt.Println(p.Address.City)  // Cũng OK
}

Method Promotion

go
type Logger struct{}

func (l Logger) Log(msg string) {
    fmt.Println("[LOG]", msg)
}

type Service struct {
    Logger  // Embedded
    Name string
}

func main() {
    s := Service{Name: "AuthService"}
    s.Log("Started")  // ← Method được promoted!
    // Tương đương: s.Logger.Log("Started")
}

🎓 Under the Hood: Embedding != Inheritance

Quan trọng: Embedded field vẫn là một field riêng biệt trong memory.

go
type Base struct { ID int }
type Derived struct { Base }

d := Derived{Base{ID: 42}}

// Memory layout:
// Derived
// └── Base
//     └── ID: 42

// KHÔNG PHẢI:
// Derived extends Base (như Java)

Hệ quả:

  • Không có super keyword
  • Không có method override (chỉ có shadowing)
  • Type assertion d.(Base) KHÔNG WORK

Methods & Receivers

Value Receiver vs Pointer Receiver

go
type Counter struct {
    Value int
}

// Value Receiver - nhận COPY của struct
func (c Counter) IncrementBad() {
    c.Value++  // Chỉ tăng bản copy, original không đổi!
}

// Pointer Receiver - nhận POINTER đến struct
func (c *Counter) IncrementGood() {
    c.Value++  // Tăng original
}

func main() {
    c := Counter{Value: 0}
    
    c.IncrementBad()
    fmt.Println(c.Value)  // 0 (không đổi!)
    
    c.IncrementGood()
    fmt.Println(c.Value)  // 1 (đã tăng)
}

Comparison Table

AspectValue Receiver (s S)Pointer Receiver (s *S)
Mutation❌ Không modify original✅ Modify original
Copy CostCopy toàn bộ structCopy 8 bytes (pointer)
Nil Safe✅ Không panic với nil⚠️ Phải check nil
Concurrency✅ Safe (immutable copy)⚠️ Cần mutex

Memory Visualization

┌─────────────────────────────────────────────────────────────────────┐
│                    Value Receiver Call                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   main() stack:           IncrementBad() stack:                     │
│   ┌────────────┐          ┌────────────┐                            │
│   │ c.Value: 0 │  ──copy─►│ c.Value: 0 │ ← Bản copy                │
│   └────────────┘          └──────┬─────┘                            │
│         ↑                        │ c.Value++ → c.Value: 1          │
│         │                        ▼                                  │
│   c.Value vẫn = 0!        (copy bị destroy khi function return)    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│                   Pointer Receiver Call                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   main() stack:           IncrementGood() stack:                    │
│   ┌────────────┐          ┌─────────────────┐                       │
│   │ c.Value: 0 │ ◄────────│ c *Counter      │                       │
│   └──────┬─────┘          │ (points to c)   │                       │
│          │                └─────────────────┘                       │
│          │                        │                                 │
│          │ c.Value++ thông qua pointer                              │
│          ▼                                                          │
│   c.Value = 1  ← Modified!                                          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

HPN Rule: When to Use Which?

📌 HPN Standard: Pointer Receiver by Default

"If in doubt, use Pointer Receiver"

Lý do:

  1. Consistency - Tất cả methods cùng style
  2. Performance - Không copy large structs
  3. Future-proof - Dễ thêm mutation logic sau

Chỉ dùng Value Receiver khi:

  • Struct rất nhỏ (< 3 fields, all primitives)
  • Method KHÔNG và KHÔNG BAO GIỜ modify state
  • Cần immutability guarantee (concurrent safe)
go
// ✅ Good: Consistent pointer receivers
type UserService struct {
    repo    UserRepository
    cache   Cache
    metrics Metrics
}

func (s *UserService) GetUser(ctx context.Context, id int64) (*User, error) { ... }
func (s *UserService) CreateUser(ctx context.Context, u *User) error { ... }
func (s *UserService) UpdateUser(ctx context.Context, u *User) error { ... }

// ✅ OK: Small immutable struct with value receiver
type Point struct {
    X, Y float64
}

func (p Point) Distance(other Point) float64 {
    dx := p.X - other.X
    dy := p.Y - other.Y
    return math.Sqrt(dx*dx + dy*dy)
}

🏷️ Struct Tags

Struct tags là metadata gắn vào fields, được đọc bởi reflect package:

go
type User struct {
    ID        int64  `json:"id" db:"user_id"`
    FirstName string `json:"first_name" db:"first_name" validate:"required"`
    Email     string `json:"email" db:"email" validate:"email"`
    Password  string `json:"-" db:"password_hash"`  // "-" = omit from JSON
    CreatedAt time.Time `json:"created_at,omitempty"`  // omitempty = skip if zero
}

Common Tag Formats

go
// JSON serialization
`json:"field_name"`           // Rename field
`json:"field_name,omitempty"` // Skip if zero value
`json:"-"`                    // Always skip

// Database mapping
`db:"column_name"`
`gorm:"primaryKey;autoIncrement"`

// Validation
`validate:"required,min=3,max=100"`
`validate:"email"`
`validate:"oneof=active inactive pending"`

// Multiple tags
`json:"email" db:"email" validate:"required,email"`

Reading Tags with Reflect

go
import "reflect"

func main() {
    t := reflect.TypeOf(User{})
    field, _ := t.FieldByName("Email")
    
    jsonTag := field.Tag.Get("json")     // "email"
    dbTag := field.Tag.Get("db")         // "email"
    validateTag := field.Tag.Get("validate")  // "required,email"
}

🔥 Raizo's Pitfall: Tag Syntax

Tag phải đúng format, không có space thừa!

go
// ❌ WRONG - có space sau colon
`json: "name"`  // Tag sẽ không parse được!

// ✅ CORRECT
`json:"name"`

📊 Summary

ConceptKey Point
Memory AlignmentOrder fields largest→smallest to minimize padding
CompositionEmbedded fields ≠ Inheritance, chỉ là field promotion
Value ReceiverCopies struct, không modify original
Pointer ReceiverReferences original, use by default
Struct TagsMetadata for JSON, DB, validation

➡️ Tiếp theo

Structs nắm vững rồi! Tiếp theo: Functions & Methods - Multiple returns, defer, closures, và variadic functions.