Lỗi gặp phải
Chương trình Go của bạn bị crash giữa chừng, in ra stack trace và thoát với status 2:
goroutine 1 [running]:
main.main()
/home/user/app/main.go:15 +0x1c
exit status 2
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47d8a6]
Nguyên nhân: code của bạn đã cố đọc hoặc ghi qua một con trỏ đang trỏ đến địa chỉ 0x0 — cách Go báo hiệu rằng "con trỏ này chưa bao giờ được gán giá trị thực." Địa chỉ đó luôn không hợp lệ, vì vậy hệ điều hành lập tức kill tiến trình.
Nguyên nhân gốc rễ
Go không tự khởi tạo các kiểu con trỏ, interface, map, slice, channel hay function value. Giá trị zero của chúng là nil. Truy cập bất kỳ thứ nào trong số đó trước khi gán giá trị thực, bạn sẽ gặp panic này.
Năm tình huống thường xuyên gây ra lỗi này trong thực tế:
- Gọi method trên con trỏ struct nil (
var u *User; u.Name()) - Bỏ qua lỗi trả về từ hàm có thể trả về
nilkhi thất bại - Truy cập map hoặc slice được khai báo bằng
varnhưng chưa được khởi tạo - Lưu trữ một interface bọc con trỏ concrete nil
- Sử dụng kết quả type assertion thất bại mà không kiểm tra
ok
Cách xử lý 1 — Kiểm tra nil trước khi sử dụng
Hãy kiểm tra mọi con trỏ hoặc interface trước khi truy cập. Chỉ cần thêm một câu lệnh if là đủ.
// Sai — panic nếu resp là nil
func processResponse(resp *http.Response) string {
return resp.Status
}
// Đúng — kiểm tra nil rõ ràng
func processResponse(resp *http.Response) string {
if resp == nil {
return "no response"
}
return resp.Status
}
Cách xử lý 2 — Luôn kiểm tra lỗi từ hàm trả về con trỏ
Đây là nguồn gốc phổ biến nhất của nil panic trong Go. Các hàm như os.Open, truy vấn database hay JSON unmarshal đều trả về (value, error). Khi lời gọi thất bại, value sẽ là nil — và nếu bạn bỏ qua lỗi bằng _, bạn chỉ còn cách một dòng so với việc bị crash.
// Sai — bỏ qua lỗi, sau đó panic vì nil
f, _ := os.Open("config.json")
data, _ := io.ReadAll(f) // f là nil nếu Open thất bại
// Đúng — kiểm tra lỗi trước
f, err := os.Open("config.json")
if err != nil {
log.Fatal(err)
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
log.Fatal(err)
}
Cách xử lý 3 — Khởi tạo struct và map trước khi sử dụng
Khai báo struct bằng var sẽ để tất cả các trường con trỏ của nó ở trạng thái nil. Bạn phải khởi tạo rõ ràng các struct lồng nhau trước khi ghi vào chúng.
// Sai — Config.DB chưa bao giờ được khởi tạo
type Config struct {
DB *DatabaseConfig
}
var cfg Config
cfg.DB.Host = "localhost" // panic: DB là nil
// Đúng — khởi tạo struct lồng nhau
cfg := Config{
DB: &DatabaseConfig{},
}
cfg.DB.Host = "localhost"
// Hoặc dùng new()
cfg.DB = new(DatabaseConfig)
cfg.DB.Host = "localhost"
Map cũng gặp vấn đề tương tự:
// Sai
var cache map[string]int
cache["key"] = 1 // panic: assignment to entry in nil map
// Đúng
cache := make(map[string]int)
cache["key"] = 1
Cách xử lý 4 — Dùng pattern comma-ok cho type assertion
Bỏ qua việc kiểm tra ok khi type assertion sẽ gây panic ngay khi kiểu không khớp. Thêm hai ký tự giúp bạn tránh được crash.
// Sai
var i interface{} = "hello"
n := i.(int) // panic: interface conversion
// Đúng
n, ok := i.(int)
if !ok {
fmt.Println("not an int")
return
}
Cách xử lý 5 — Đọc stack trace để tìm dòng code chính xác
Đừng đoán mò — output của panic cho bạn biết chính xác nơi xảy ra lỗi. Đặt GOTRACEBACK=all trước khi bắt đầu điều tra:
GOTRACEBACK=all go run main.go
Tìm trong trace frame đầu tiên thuộc package của bạn, không phải các phần nội bộ của Go runtime. Đó chính là chỗ xảy ra nil dereference. Ví dụ cụ thể:
panic: runtime error: invalid memory address or nil pointer dereference
goroutine 1 [running]:
main.processUser(...)
/home/user/app/main.go:23 ← code của bạn, dòng 23
main.main()
/home/user/app/main.go:10 +0x68
Chuyển đến dòng 23 trong main.go và tìm con trỏ hoặc interface được sử dụng mà không có kiểm tra nil.
Cách xử lý 6 — Chỉ dùng recover như phương án cuối cùng
Đang xây dựng server hoặc middleware cần chịu được panic bất ngờ? Hãy bọc các lời gọi rủi ro bằng defer + recover. Nhưng cần hiểu rõ đây là gì: một lưới an toàn, không phải bản vá lỗi. Bạn vẫn cần tìm ra nguyên nhân nil và xử lý đúng cách.
func safeCall(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered from panic: %v", r)
}
}()
fn()
return nil
}
Kiểm tra kết quả
Chạy lại chương trình và xác nhận panic đã biến mất:
go run main.go
Tốt hơn nữa, hãy cố định bản sửa lỗi bằng unit test để đảm bảo lỗi không âm thầm tái xuất:
func TestProcessResponseNil(t *testing.T) {
result := processResponse(nil)
if result != "no response" {
t.Errorf("expected 'no response', got %q", result)
}
}
Chạy go test ./... — kết quả xanh không có panic nghĩa là bản sửa lỗi đã hoạt động.
Phòng ngừa
- Chạy với race detector trong lúc phát triển:
go run -race main.go— phát hiện các nil write đồng thời gần như không thể tái hiện theo cách khác. - Chạy
go vet ./...trước mỗi lần commit; lệnh này phát hiện tĩnh một số pattern nil dereference mà không tốn thêm chi phí. - staticcheck và
golangci-lintphát hiện rủi ro nil pointer màgo vetbỏ sót — đáng để tích hợp vào CI. - Trả về lỗi cụ thể thay vì con trỏ nil khi hàm thất bại. Điều này khiến nil không thể bị bỏ qua âm thầm.
- Với các struct chỉ tồn tại trên heap, hãy viết constructor:
func NewUser() *User { return &User{scores: make([]int, 0)} }— người gọi luôn nhận được giá trị đã được khởi tạo.

