Skip to content

📦 Variables, Types & Memory

Trong Go, mọi thứ đều có Zero Value. Đây là design decision quan trọng nhất mà bạn cần hiểu trước khi viết production code.

🔢 Basic Types & Zero Values

Go có một tập hợp primitive types đơn giản và nhất quán:

TypeZero ValueVí dụ
int, int8, int16, int32, int640var count int0
uint, uint8 (byte), uint16, uint32, uint640var flags uint80
float32, float640.0var rate float640.0
boolfalsevar isActive boolfalse
string"" (empty string)var name string""
*T (pointer)nilvar ptr *Usernil

🎓 Professor Tom's Deep Dive: Zero Values Philosophy

Tại sao Go dùng Zero Values thay vì undefined/null?

  1. Memory Safety: Mọi variable đều có giá trị hợp lệ ngay từ khi khai báo
  2. No Constructors Needed: Struct có thể sử dụng ngay với zero values
  3. Predictable Behavior: Code đọc được mà không cần trace initialization
go
// Trong Go - zero value sẵn sàng sử dụng
type Config struct {
    MaxRetries int    // 0 - hợp lý cho default
    Timeout    int    // 0 - có thể cần set
    Debug      bool   // false - production-safe default
}

cfg := Config{} // Hoàn toàn hợp lệ, không panic

So sánh với Java:

java
// Java - NullPointerException waiting to happen
Config cfg = new Config(); // Phải có constructor
cfg.name.length();         // 💥 NullPointerException

Kích thước và Platform

go
// Platform-dependent sizes
var i int   // 32-bit trên 32-bit OS, 64-bit trên 64-bit OS
var p uintptr // Đủ lớn để chứa pointer

// Fixed-size types - dùng khi cần control chính xác
var userId int64      // Luôn 8 bytes
var statusCode int32  // Luôn 4 bytes
var flags byte        // Alias cho uint8, 1 byte

📌 HPN Standard

Quy tắc chọn type:

  • API responses, database IDs: int64 (explicit, cross-platform)
  • Loop counters, indices: int (idiomatic)
  • Byte manipulation: byte hoặc uint8
  • Money/Currency: KHÔNG dùng float → dùng int64 (cents) hoặc decimal library

📝 Strings: Immutable & Efficient

🎓 Under the Hood: String Header

Trong Go, string không phải là array of characters. Nó là một struct gồm 2 fields:

┌─────────────────────────────────────────────┐
│           String Header (16 bytes)          │
├─────────────────────────────────────────────┤
│  ptr *byte  │  len int                      │
│  (8 bytes)  │  (8 bytes)                    │
└──────┬──────┴───────────────────────────────┘


┌──────────────────────────────────────────────┐
│  H │ e │ l │ l │ o │ , │   │ W │ o │ r │ l │ d │
│ 48 │65 │6C │6C │6F │2C │20 │57 │6F │72 │6C │64 │
└──────────────────────────────────────────────┘
        Underlying byte array (UTF-8 encoded)

Immutability Implications

go
s := "Hello"
// s[0] = 'h'  // ❌ Compile error: cannot assign to s[0]

// Để modify, phải convert sang []byte (tạo copy mới)
b := []byte(s)
b[0] = 'h'
s2 := string(b) // "hello" - string MỚI

String Operations Cost

go
// ❌ Bad: O(n²) - mỗi += tạo string mới
func buildBad(items []string) string {
    result := ""
    for _, item := range items {
        result += item + ","  // Allocates new string each time!
    }
    return result
}

// ✅ Good: O(n) - sử dụng strings.Builder
func buildGood(items []string) string {
    var sb strings.Builder
    for _, item := range items {
        sb.WriteString(item)
        sb.WriteString(",")
    }
    return sb.String()
}

🔥 HPN's Pitfall

String length vs Rune count:

go
s := "Việt Nam"
len(s)                    // 11 (bytes, không phải characters!)
utf8.RuneCountInString(s) // 8 (actual characters)

// Iteration khác nhau:
for i := 0; i < len(s); i++ { /* byte by byte */ }
for _, r := range s { /* rune by rune - đúng cho Unicode */ }

🎯 Pointers: The Core Concept

Syntax: *T vs &T

go
var userID int = 42

// &T - Address-of operator: lấy địa chỉ
ptr := &userID  // ptr có type *int, chứa địa chỉ của userID

// *T - Dereference operator: lấy giá trị tại địa chỉ
value := *ptr   // value = 42
*ptr = 100      // userID giờ = 100

Memory Visualization

┌─────────────────────────────────────────────────────────┐
│                      STACK FRAME                        │
├─────────────────────────────────────────────────────────┤
│                                                         │
│   userID    ┌──────┐                                    │
│   (int)     │  42  │  ← Address: 0xc0000140a8          │
│             └──────┘                                    │
│                 ▲                                       │
│                 │                                       │
│   ptr       ┌──────────────┐                            │
│   (*int)    │ 0xc0000140a8 │  ← Points to userID       │
│             └──────────────┘                            │
│                                                         │
└─────────────────────────────────────────────────────────┘

🎓 Professor Tom's Deep Dive: Why Go Pointers are "Safe"

Go loại bỏ Pointer Arithmetic hoàn toàn:

c
// C - Dangerous pointer arithmetic
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
p++;           // Move to next element
*(p + 10);     // 💥 Buffer overflow - undefined behavior
go
// Go - Không có pointer arithmetic
arr := [5]int{1, 2, 3, 4, 5}
p := &arr[0]
// p++          // ❌ Compile error!
// *(p + 10)    // ❌ Compile error!

Kết quả:

  • Không có buffer overflow qua pointer manipulation
  • Không có wild pointers (trỏ đến memory ngẫu nhiên)
  • GC có thể track tất cả pointers một cách an toàn

Pass by Value vs Pass by Pointer

go
type Order struct {
    ID       int64
    Items    []string
    Total    float64
    Metadata map[string]string
}

// Pass by Value - COPY toàn bộ struct
func processValue(o Order) {
    o.Total = 999  // Chỉ modify bản copy!
}

// Pass by Pointer - Chỉ copy địa chỉ (8 bytes)
func processPointer(o *Order) {
    o.Total = 999  // Modify original!
}

Memory Layout khi gọi function:

┌─────────────────────────────────────────────────────────────┐
│                    CALLER STACK FRAME                       │
├─────────────────────────────────────────────────────────────┤
│   order (Order)                                             │
│   ┌────────────────────────────────────────────────────┐    │
│   │ ID: 12345 │ Items: [...] │ Total: 99.99 │ Meta... │    │
│   └────────────────────────────────────────────────────┘    │
│                            │                                │
└────────────────────────────┼────────────────────────────────┘

    ┌────────────────────────┴────────────────────────────┐
    │                                                      │
    ▼ processValue(o Order)         ▼ processPointer(o *Order)
┌─────────────────────────┐    ┌─────────────────────────┐
│   CALLEE STACK FRAME    │    │   CALLEE STACK FRAME    │
├─────────────────────────┤    ├─────────────────────────┤
│   o (Order) - COPY!     │    │   o (*Order) - 8 bytes  │
│   ┌───────────────────┐ │    │   ┌─────────────────┐   │
│   │ ID │Items│Total...│ │    │   │ 0xc00012a000    │───┼──► points to original
│   └───────────────────┘ │    │   └─────────────────┘   │
│   ~100+ bytes copied    │    │   Only 8 bytes copied   │
└─────────────────────────┘    └─────────────────────────┘

Variable Declaration: var vs :=

go
// var - Explicit declaration (có thể dùng ở package level)
var maxConnections int = 100
var defaultTimeout int            // Zero value: 0
var (
    host string = "localhost"
    port int    = 8080
)

// := - Short declaration (chỉ trong function)
func handler() {
    conn := NewConnection()       // Type inference
    count := 0                    // int
    rate := 3.14                  // float64
}

📌 HPN Standard: Khi nào dùng gì?

SituationSyntaxLý do
Package-level variablesvar:= không hợp lệ ngoài function
Khai báo với zero valuevar x intExplicit về intent
Khai báo với initial valuex := 42Concise, idiomatic
Type khác với inferencevar x int64 = 4242 mặc định là int

🧠 Escape Analysis: Stack vs Heap

🔥 HPN's Pitfall

Đừng assume pointers luôn nhanh hơn!

Nhiều developers từ C/C++ nghĩ: "Pointer = không copy = nhanh hơn". Sai!

go
// Case 1: Small struct - Value có thể NHANH hơn
type Point struct {
    X, Y float64  // 16 bytes total
}

func processPoint(p Point) { /* ... */ }   // Stack allocation, no GC
func processPointPtr(p *Point) { /* ... */ } // Có thể escape to heap!

// Case 2: Large struct - Pointer thường tốt hơn
type BigData struct {
    Buffer [1024]byte
    // ... nhiều fields
}

func processBig(d *BigData) { /* ... */ }  // Chỉ copy 8 bytes

Escape Analysis Explained

go
func createUser() *User {
    u := User{Name: "HPN"}  // Khai báo local
    return &u                  // Return pointer → MUST escape to heap!
}

// Compiler decision:
// "u" escapes to heap because it's returned as pointer
// Heap allocation = GC pressure

Kiểm tra Escape Analysis:

bash
# Xem compiler quyết định gì
$ go build -gcflags="-m" ./...

./handler.go:15:2: moved to heap: u
./handler.go:20:9: req does not escape

Memory implications:

┌─────────────────────────────────────────────────────────────┐
│                         STACK                               │
│   - Automatic cleanup khi function return                   │
│   - O(1) allocation (chỉ move stack pointer)               │
│   - Cache-friendly (contiguous memory)                      │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                          HEAP                               │
│   - Cần GC để cleanup (stop-the-world pause)               │
│   - Slower allocation (memory management overhead)          │
│   - May cause memory fragmentation                          │
└─────────────────────────────────────────────────────────────┘

📌 HPN Standard: Escape Analysis Rules of Thumb

  1. Return pointer to local var → Escapes
  2. Store in interface{} → Usually escapes
  3. Closure captures pointer → May escape
  4. Large allocations (over 32KB) → Always heap
  5. Slices with unknown size at compile time → Heap

🐛 Spot the Bug: Pointer Edition

🐛 Bug Hunt Challenge: Nil Pointer Trap

Đoạn code sau sẽ crash. Bạn có thể tìm ra lỗi không?

go
func getUserName(u *User) string {
    return u.Name  // 💥 Line có bug!
}

func main() {
    var user *User
    name := getUserName(user)
    fmt.Println(name)
}
💡 Gợi ý

Kiểm tra zero value của pointer là gì?

🔍 Giải thích chi tiết

Bug: var user *User khởi tạo user với zero value là nil. Khi gọi getUserName(user), ta truyền nil pointer. Truy cập u.Name khi unilpanic: runtime error: invalid memory address

Fix 1 - Nil check:

go
func getUserName(u *User) string {
    if u == nil {
        return ""
    }
    return u.Name
}

Fix 2 - Không dùng pointer nếu không cần:

go
func getUserName(u User) string {
    return u.Name  // Safe - u là copy, luôn có zero value hợp lệ
}

📚 Tổng kết

ConceptKey Takeaway
Zero ValuesMọi type đều có default value hợp lệ
StringsImmutable, (ptr, len) structure, UTF-8
PointersSafe (no arithmetic), dùng cho mutation/large data
var vs :=var cho package-level, := cho function-level
Escape AnalysisCompiler quyết định Stack/Heap, không phải bạn

➡️ Tiếp theo

Bạn đã hiểu memory model. Tiếp theo: Control Structures - Cách Go xử lý if/switch/for khác biệt so với các ngôn ngữ khác.