Tham số ...T
Đặt ... trước kiểu của tham số cuối cùng. Khi đó nơi gọi hàm có thể truyền bao nhiêu đối số kiểu đó cũng được, kể cả không truyền gì. Bên trong hàm, tham số này là một slice.
Chương trình in ra 0, 5 và 10. Khi không có đối số nào, nums là một slice nil có độ dài 0, nên vòng lặp đơn giản là không chạy.
Các tham số thường có thể đứng trước. Chỉ tham số cuối cùng được phép là variadic:
func logf(level string, format string, args ...any)
Truyền slice bằng s...
Nếu đã có sẵn một slice, bạn không thể truyền thẳng nó vào: sum(nums) báo lỗi cannot use nums (variable of type []int) as int value in argument to sum. Hãy trải slice ra bằng ... đặt sau đối số:
append cũng là hàm variadic, vì thế nối hai slice được viết là append(a, b...).
Slice được trải ra phải là thứ duy nhất ở vị trí variadic. sum(1, nums...) báo lỗi too many arguments in call to sum. Hãy dựng slice gộp trước bằng append.
Slice được dùng chung, không sao chép
Khi bạn gọi hàm với các đối số riêng lẻ, Go tạo một slice mới cho chúng. Khi bạn trải một slice có sẵn bằng s..., hàm nhận đúng slice đó. Ghi vào phần tử của nó sẽ làm thay đổi dữ liệu của nơi gọi.
Đầu ra:
[7 8 9]
[0 8 9]
Một hàm variadic có sửa tham số của mình thì nên ghi rõ điều đó trong tài liệu, hoặc sao chép trước bằng slices.Clone. Phần lớn hàm variadic chỉ đọc đối số nên hiếm khi gặp vấn đề này, nhưng đây là điểm khác biệt thật so với các ngôn ngữ mà tham số rest luôn là một mảng mới.
...any và cách fmt.Println hoạt động
fmt.Println được khai báo là func Println(a ...any) (n int, err error). any (bí danh của interface{} từ Go 1.18) nhận mọi kiểu, nên bạn truyền chuỗi, số và struct trong cùng một lời gọi được.
Chi tiết quan trọng là việc chuyển tiếp: logf truyền args... cho Printf. Thiếu dấu ba chấm, Printf chỉ nhận một đối số là một []any, và dòng cảnh báo in ra [WARN] [3 8] of %!d(MISSING) workers idle. go vet chỉ báo missing ... in args forwarded to printf-like function khi tham số format được truyền nguyên vẹn, như fmt.Printf(format, args). Ở đây format được ghép bằng +, nên vet im lặng và việc nhớ dấu ba chấm là của bạn.
Không thể trải một []string vào ...any. Kiểu phần tử phải khớp chính xác, nên hãy chuyển đổi trước:
names := []string{"a", "b"}
args := make([]any, len(names))
for i, n := range names {
args[i] = n
}
fmt.Println(args...)
Bắt buộc có ít nhất một đối số
Tham số variadic chấp nhận không có đối số nào. Khi hàm cần ít nhất một đối số, hãy biến đối số đầu tiên thành tham số thường:
Giờ maxOf() là lỗi biên dịch thay vì phải kiểm tra lúc chạy. Bản thân hàm có sẵn max cũng hoạt động như vậy: nó đòi ít nhất một đối số.
Tham số variadic làm đối số tùy chọn
Go không có giá trị mặc định cho tham số. Đôi khi người ta dùng tham số variadic để giả lập một đối số tùy chọn:
func connect(addr string, timeout ...time.Duration)
Cách này chạy được nhưng khó đọc, vì nơi gọi có thể truyền ba timeout và chữ ký hàm không cho biết cái nào được dùng. Khi có nhiều hơn một tùy chọn, nên dùng một struct cấu hình, hoặc mẫu functional options (...Option, trong đó mỗi Option là một hàm gán giá trị cho một trường). Mẫu này cũng là variadic, nhưng mỗi đối số đều có ý nghĩa rõ ràng.
Tóm tắt quy tắc
| Quy tắc | Ví dụ |
|---|---|
| Chỉ tham số cuối cùng được là variadic | func f(a string, b ...int) |
| Bên trong hàm, tham số là một slice | b có kiểu []int |
| Không có đối số thì nhận slice nil | f("x") cho b == nil |
Trải slice bằng ... | f("x", nums...) |
| Trải slice thì dùng chung mảng bên dưới | ghi vào b[i] làm thay đổi nums |
| Không trộn slice được trải với giá trị khác | f("x", 1, nums...) không biên dịch được |
Câu hỏi thường gặp
Hàm variadic trong Go là gì?
Là hàm có tham số cuối cùng ở dạng ...T, như func sum(nums ...int) int. Bạn gọi nó với không hoặc nhiều đối số kiểu T, và bên trong hàm, tham số đó là một slice []T.
Truyền một slice vào hàm variadic trong Go như thế nào?
Thêm ... sau slice: sum(nums...). Slice được truyền nguyên vẹn, không sao chép, nên hàm thấy đúng mảng nằm bên dưới. Bạn không thể trộn slice được trải ra với các đối số riêng lẻ: sum(1, nums...) không biên dịch được.
Go có toán tử spread không?
Chỉ có cho đối số cuối cùng của một lời gọi variadic: f(s...). Không có cú pháp spread chung để dựng mảng, struct, hay gọi hàm không variadic bằng một slice. append(a, b...) chính là quy tắc đó áp dụng cho hàm có sẵn append.
Một hàm Go có thể có hai tham số variadic không?
Không. Chỉ tham số cuối cùng được là variadic, và mỗi hàm chỉ có một. Nếu cần thêm danh sách khác, hãy truyền chúng như tham số slice bình thường.