Quá trình Refactor thất bạiCuối cùng bạn cũng đang nâng cấp một dịch vụ Go cũ để sử dụng generics. Bạn quyết định bắt đầu từ những việc nhỏ với một hàm tiện ích để kiểm tra xem một slice có chứa một giá trị cụ thể hay không. Logic rất đơn giản, vì vậy bạn viết như sau:
func Contains[T any](slice []T, target T) bool {
for _, v := range slice {
if v == target {
return true
}
}
return false
}
Bạn chạy lệnh go build và mong đợi một thông báo thành công. Thay vào đó, trình biên dịch chặn bạn bằng một lỗi gây khó chịu:
./main.go:10:10: invalid operation: v == target (type T does not satisfy comparable)
Tại sao Go chặn quá trình Build của bạnTừ khóa any là một bí danh cho interface{}. Một sai lầm phổ biến là giả định rằng any có nghĩa là 'hỗ trợ tất cả các thao tác'. Thực tế, trình biên dịch của Go cực kỳ bảo thủ. Nó sẽ không cho phép bạn sử dụng toán tử so sánh bằng (==) trừ khi nó có thể đảm bảo rằng kiểu dữ liệu đó hỗ trợ toán tử này.
Không phải mọi kiểu dữ liệu trong Go đều có thể so sánh được. Slices, maps và functions là những đối tượng chính gây ra vấn đề này. Nếu bạn truyền một slice chứa các slice vào hàm trên, biểu thức v == target sẽ gây ra lỗi runtime panic. Để ngăn chặn điều này, Go yêu cầu bạn phải nêu rõ các yêu cầu đối với T.
Cách khắc phục nhanh: Ràng buộc 'comparable'Giải pháp nhanh nhất là thay đổi any thành ràng buộc comparable có sẵn. Ràng buộc này giới hạn T trong các kiểu dữ liệu hoạt động được với == và !=. Điều này bao gồm các kiểu booleans, numbers, strings, pointers và channels. Nó cũng bao gồm các struct, với điều kiện tất cả các trường bên trong chúng cũng có thể so sánh được.
Mã nguồn sau khi sửa```
// Thay đổi [T any] thành [T comparable] func Contains[T comparable](slice []T, target T) bool { for _, v := range slice { if v == target { return true } } return false }
Bây giờ trình biên dịch đã chấp nhận. Nếu bạn thử truyền `[]int`, nó sẽ hoạt động. Nếu bạn thử truyền `[][]string`, trình biên dịch sẽ phát hiện lỗi ngay tại thời điểm build thay vì để ứng dụng của bạn bị crash khi đang chạy thực tế.
## Xử lý các kiểu dữ liệu không thể so sánh đượcĐiều gì xảy ra nếu bạn cần so sánh các dữ liệu phức tạp? Nếu struct của bạn chứa một slice hoặc một map, ràng buộc `comparable` sẽ không hoạt động. Trong những trường hợp này, bạn nên tuân theo mô hình được thiết lập trong package `slices` của Go 1.21: truyền vào một hàm so sánh.
### Ví dụ: Sử dụng hàm so sánh```
func ContainsFunc[T any](slice []T, target T, equals func(T, T) bool) bool {
for _, v := range slice {
if equals(v, target) {
return true
}
}
return false
}
// Sử dụng với một struct không thể so sánh được
type User struct {
ID int
Tags []string // Slice làm cho struct này không thể so sánh được
}
users := []User{{ID: 1, Tags: []string{"admin"}}, {ID: 2}}
target := User{ID: 1}
found := ContainsFunc(users, target, func(a, b User) bool {
return a.ID == b.ID
})
Xác minh và Kiểm thửHãy làm theo các bước sau để đảm bảo bản sửa lỗi của bạn hoạt động ổn định:
- Xác minh Signature: Kiểm tra xem tham số generic của bạn có phải là
[T comparable]hay không.- Chạy bộ kiểm thử: Thực thi lệnhgo test ./.... Nếu quá trình biên dịch thành công, các kiểu dữ liệu của bạn đã thỏa mãn ràng buộc.- Kiểm tra giới hạn: Thử truyền một slice chứa các map ([]map[string]string) vào hàm. Bạn sẽ thấy một lỗi biên dịch rõ ràng. Điều này xác nhận rằng ràng buộc đang hoạt động đúng chức năng.## Tổng kếtLỗitype T does not satisfy comparablechỉ là cách Go yêu cầu một sự đảm bảo về an toàn kiểu dữ liệu (type safety). Hãy sử dụngcomparablecho các kiểu đơn giản như string hoặc integer. Đối với các kiểu phức tạp như slice hoặc map, hãy bỏ qua toán tử so sánh bằng và sử dụng hàm so sánh tùy chỉnh để thay thế.

