TL;DR: Cách sửa nhanh
Crash này xảy ra vì bạn khai báo biến map nhưng chưa cấp phát bộ nhớ cho nó. Go cho phép đọc từ map nil, nhưng ghi vào map nil sẽ gây panic ngay lập tức.
Cách sai:
var counts map[string]int
counts["users"] = 10 // ❌ PANIC Ở ĐÂY
Cách đúng (dùng make):
counts := make(map[string]int)
counts["users"] = 10 // ✅ Hoạt động bình thường
Cách đúng (dùng literal):
counts := map[string]int{}
counts["users"] = 10 // ✅ Hoạt động bình thường
Tại sao lỗi này xảy ra?
Map trong Go là kiểu tham chiếu. Khi bạn viết var m map[K]V, giá trị ban đầu của m là nil. Lúc này nó chưa trỏ đến một bảng băm đã được khởi tạo.
Go khá dễ chịu với map nil trong các thao tác đọc. Nếu bạn truy cập một key trong map nil, Go chỉ trả về giá trị zero của kiểu đó (như 0 với số nguyên hay "" với chuỗi). Tuy nhiên, runtime lại có giới hạn cứng với thao tác ghi. Go sẽ không tự động cấp phát bộ nhớ khi bạn gán giá trị, thay vào đó sẽ dừng chương trình để tránh hỏng bộ nhớ.
Các lỗi thường gặp và cách khắc phục
1. Biến toàn cục hoặc biến cục bộ chưa khởi tạo
Đây là lỗi phổ biến nhất. Bạn định nghĩa map để theo dõi thứ gì đó — chẳng hạn mã phản hồi HTTP — nhưng bỏ qua bước khởi tạo.
func trackStatus() {
var statusCodes map[int]string
// ... xử lý logic
statusCodes[200] = "OK" // Lỗi: assignment to entry in nil map
}
Cách sửa: Luôn dùng make(). Nếu biết trước sẽ lưu khoảng 100 phần tử, bạn có thể cấp phát trước với make(map[int]string, 100) để cải thiện hiệu năng.
2. Map bên trong Struct
Khởi tạo struct không tự động khởi tạo các map bên trong nó. Đây là nguồn gốc lỗi thường gặp trong các ứng dụng lớn hơn.
type UserProfile struct {
ID int
Settings map[string]string
}
func main() {
u := UserProfile{ID: 1}
u.Settings["theme"] = "dark" // ❌ PANIC: Settings là nil
}
Cách sửa: Dùng hàm khởi tạo (constructor). Pattern này đảm bảo struct luôn sẵn sàng sử dụng ngay sau khi tạo.
func NewUserProfile(id int) *UserProfile {
return &UserProfile{
ID: id,
Settings: make(map[string]string),
}
}
3. Khởi tạo trễ với JSON
Package encoding/json khá thông minh. Nếu bạn unmarshal dữ liệu JSON vào map nil, Go sẽ tự khởi tạo cho bạn. Nhưng nếu JSON đầu vào rỗng hoặc quá trình unmarshal thất bại, map vẫn giữ nguyên giá trị nil.
var data map[string]interface{}
// Nếu json.Unmarshal không được gọi hoặc thất bại, data vẫn là nil
err := json.Unmarshal(rawBytes, &data)
// Logic giả sử 'data' đã sẵn sàng sẽ crash ở đây
data["updated_at"] = time.Now().Unix()
Cách kiểm tra và phòng tránh lỗi này
Để giữ code ổn định, hãy thực hiện ba bước thực tế sau:
- **Chạy phân tích tĩnh:** Các công cụ như `go vet` hoặc `golangci-lint` thường phát hiện được map chưa khởi tạo trước khi bạn chạy code.
- **Kiểm tra nil phòng thủ:** Nếu một hàm nhận map từ nguồn bên ngoài, hãy kiểm tra nó. Một câu lệnh đơn giản `if m == nil { m = make(map[K]V) }` có thể cứu môi trường production của bạn khỏi crash.
- **Unit test:** Viết test mô phỏng đầu vào rỗng. Nếu logic của bạn cố ghi vào map chưa khởi tạo, test sẽ báo lỗi rõ ràng.
Bảng tóm tắt
Thao tác
Kết quả trên Map Nil
`val := m["key"]`
An toàn (trả về giá trị zero)
`len(m)`
An toàn (trả về 0)
`delete(m, "key")`
An toàn (không làm gì)
`m["key"] = "val"`
**Panic (Crash)**

