Go không quyết định stack/heap theo new hay &. Compiler chạy escape analysis: nếu nó chứng minh được không còn tham chiếu nào tới biến sau khi hàm trả về, biến nằm trên stack (giải phóng miễn phí); nếu biến "thoát" khỏi phạm vi hàm thì chuyển lên heap và do GC quản lý.
Kiểm tra bằng cờ compiler:
go build -gcflags='-m -l' ./...
# ./main.go:12:6: can inline newUser
# ./main.go:13:9: &User{...} escapes to heapNhững nguyên nhân escape thường gặp:
- Trả về con trỏ tới biến cục bộ, hoặc gán nó vào field của object sống lâu hơn.
- Đưa giá trị vào interface — vd fmt.Println(x) nhận ...any, nên x thường escape.
- Slice/map có kích thước không biết lúc biên dịch (make([]byte, n) với n là biến).
- Biến bị closure bắt và closure sống lâu hơn hàm.
Cách xử lý trên đường nóng:
- Truyền struct nhỏ theo giá trị thay vì con trỏ khi nó chỉ được đọc.
- Cấp phát trước make([]T, 0, n) để tránh append cấp phát nhiều lần.
- Tái sử dụng buffer (sync.Pool, hoặc buffer gắn theo request) thay vì cấp phát mới mỗi lần.
Lưu ý: đây là chi tiết cài đặt, không phải cam kết ngôn ngữ. Đừng tối ưu mù — đo bằng -benchmem và heap profile trước, rồi mới đọc -gcflags=-m để hiểu vì sao.