Giao diện
📦 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:
| Type | Zero Value | Ví dụ |
|---|---|---|
int, int8, int16, int32, int64 | 0 | var count int → 0 |
uint, uint8 (byte), uint16, uint32, uint64 | 0 | var flags uint8 → 0 |
float32, float64 | 0.0 | var rate float64 → 0.0 |
bool | false | var isActive bool → false |
string | "" (empty string) | var name string → "" |
*T (pointer) | nil | var ptr *User → nil |
🎓 Professor Tom's Deep Dive: Zero Values Philosophy
Tại sao Go dùng Zero Values thay vì undefined/null?
- Memory Safety: Mọi variable đều có giá trị hợp lệ ngay từ khi khai báo
- No Constructors Needed: Struct có thể sử dụng ngay với zero values
- 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 panicSo sánh với Java:
java
// Java - NullPointerException waiting to happen
Config cfg = new Config(); // Phải có constructor
cfg.name.length(); // 💥 NullPointerExceptionKí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:
bytehoặcuint8 - Money/Currency: KHÔNG dùng float → dùng
int64(cents) hoặcdecimallibrary
📝 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ỚIString 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ờ = 100Memory 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 behaviorgo
// 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ì?
| Situation | Syntax | Lý do |
|---|---|---|
| Package-level variables | var | := không hợp lệ ngoài function |
| Khai báo với zero value | var x int | Explicit về intent |
| Khai báo với initial value | x := 42 | Concise, idiomatic |
| Type khác với inference | var x int64 = 42 | 42 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 bytesEscape 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 pressureKiể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 escapeMemory 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
- Return pointer to local var → Escapes
- Store in interface{} → Usually escapes
- Closure captures pointer → May escape
- Large allocations (over 32KB) → Always heap
- 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 u là nil → panic: 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
| Concept | Key Takeaway |
|---|---|
| Zero Values | Mọi type đều có default value hợp lệ |
| Strings | Immutable, (ptr, len) structure, UTF-8 |
| Pointers | Safe (no arithmetic), dùng cho mutation/large data |
var vs := | var cho package-level, := cho function-level |
| Escape Analysis | Compiler 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.