Sửa lỗi Go: Tại sao không thể truyền []string vào []interface{}

intermedi🔷 Go2026-06-10| Go (Golang) 1.0+, Generics yêu cầu 1.18+

Error Message

cannot use data (type []string) as type []interface {} in argument to process
#go#slice#interface#type-conversion#generics

Vấn đề

Đây là một cái bẫy phổ biến: bạn có một slice các chuỗi (string) và một hàm chấp nhận []interface{}. Vì một string đơn lẻ hoàn toàn phù hợp với một interface{}, bạn giả định rằng phiên bản slice cũng sẽ hoạt động. Tuy nhiên, trình biên dịch Go sẽ chặn điều này mọi lúc.

Bạn có thể gặp phải rào cản này khi truyền dữ liệu vào driver cơ sở dữ liệu, bộ mã hóa JSON hoặc một wrapper logging tùy chỉnh. Đây là đoạn mã thường gây ra sự ức chế này:

package main

import "fmt"

func process(items []interface{}) {
    for _, item := range items {
        fmt.Println(item)
    }
}

func main() {
    data := []string{"apple", "banana", "cherry"}
    
    // Điều này gây ra lỗi:
    // cannot use data (type []string) as type []interface {} in argument to process
    process(data) 
}

Tại sao Go từ chối điều này

Go nổi tiếng với triết lý "không phép màu" (no magic). Mặc dù một string thỏa mãn interface{}, nhưng bố cục bộ nhớ của một []string về cơ bản khác với một []interface{}.

Hãy nghĩ theo cách này: trên hệ thống 64-bit, header của một string là 16 byte (một con trỏ tới dữ liệu và một độ dài). Một interface{} cũng là 16 byte, nhưng nó bao gồm một con trỏ kiểu (type pointer) và một con trỏ dữ liệu (data pointer). Vì các cấu trúc nội bộ khác nhau, Go không thể đơn giản coi cái này là cái kia.

Nếu Go thực hiện chuyển đổi này một cách tự động, nó sẽ phải lặp qua toàn bộ slice của bạn và cấp phát lại mọi phần tử. Đây là một thao tác O(n). Để giữ cho hiệu suất có thể dự đoán được, Go buộc bạn phải làm cho chi phí này trở nên rõ ràng trong mã nguồn thay vì ẩn nó sau một phép gán đơn giản.

Giải pháp 1: Phương pháp "Vòng lặp For" (Tiêu chuẩn)

Cách khắc phục trực tiếp nhất là cấp phát thủ công một slice mới và sao chép các phần tử. Điều này làm cho "chi phí" hiệu suất—cấp phát bộ nhớ mới và tiêu tốn chu kỳ CPU—trở nên rõ ràng với bất kỳ ai đọc mã của bạn.

func main() {
    data := []string{"apple", "banana", "cherry"}

    // Cấp phát một slice mới với kiểu dữ liệu chính xác
    interfaceSlice := make([]interface{}, len(data))

    for i, v := range data {
        interfaceSlice[i] = v
    }

    process(interfaceSlice)
}

Giải pháp 2: Hàm Variadic (Tham số biến đổi)

Nếu bạn có thể thay đổi chữ ký hàm, hãy sử dụng tham số variadic (...interface{}). Điều này cho phép bạn truyền các phần tử riêng lẻ. Mặc dù nó không bỏ qua việc chuyển đổi, nhưng nó thường dẫn đến mã sạch hơn tại nơi gọi khi xử lý các tập dữ liệu nhỏ.

// Sử dụng ...interface{} thay vì []interface{}
func process(items ...interface{}) {
    for _, item := range items {
        fmt.Println(item)
    }
}

func main() {
    data := []string{"apple", "banana", "cherry"}

    // Bạn vẫn cần chuyển đổi slice thủ công
    vals := make([]interface{}, len(data))
    for i, v := range data {
        vals[i] = v
    }

    // Truyền các phần tử bằng toán tử spread
    process(vals...)
}

Giải pháp 3: Cách hiện đại (Generics)

Nếu bạn đang sử dụng Go 1.18 trở lên, hãy ngừng sử dụng []interface{} cho việc này. Generics là lựa chọn ưu việt hơn. Thay vì ép dữ liệu của bạn vào một container interface chung chung, hãy để hàm chấp nhận một slice của bất kỳ kiểu T nào.

// Hàm generic chấp nhận một slice của bất kỳ kiểu T nào
func process[T any](items []T) {
    for _, item := range items {
        fmt.Println(item)
    }
}

func main() {
    data := []string{"apple", "banana", "cherry"}
    nums := []int{10, 20, 30}

    // Không cần chuyển đổi thủ công!
    process(data)
    process(nums)
}

Xác minh và Hiệu suất

- **Kiểm tra biên dịch:** Chạy `go build`. Nếu lỗi biến mất, các kiểu dữ liệu của bạn đã được căn chỉnh chính xác.
- **Kiểm tra bộ nhớ:** Nếu bạn đang chuyển đổi một slice với 1.000.000 chuỗi, Giải pháp 1 sẽ cấp phát khoảng 16MB bộ nhớ mới cho slice interface. Hãy sử dụng benchmark nếu việc này xảy ra trong một vòng lặp tần suất cao.
- **An toàn kiểu dữ liệu:** Giải pháp 3 (Generics) là an toàn nhất. Nó bảo toàn thông tin kiểu dữ liệu cơ bản, trong khi `interface{}` làm mất nó cho đến khi bạn thực hiện ép kiểu (type assertion).

Những điểm chính cần lưu ý

- Slice không có tính hiệp biến (covariant). `[]T` không bao giờ là `[]interface{}`, bất kể `T` là gì.
- Chuyển đổi tường minh là một tính năng của Go, không phải là lỗi. Nó ngăn chặn các tác động hiệu suất O(n) bị ẩn giấu.
- Generics (`[T any]`) nên là lựa chọn đầu tiên của bạn để viết các hàm linh hoạt trong các dự án Go hiện đại.

Related Error Notes