...T 매개변수
마지막 매개변수의 타입 앞에 ...을 붙입니다. 그러면 호출하는 쪽에서 그 타입의 인자를 몇 개든, 0개도 넘길 수 있습니다. 함수 안에서 이 매개변수는 슬라이스입니다.
이 코드는 0, 5, 10을 출력합니다. 인자가 없으면 nums는 길이가 0인 nil 슬라이스이므로 반복문이 그냥 실행되지 않습니다.
일반 매개변수를 앞에 둘 수 있습니다. 가변일 수 있는 건 마지막 매개변수뿐입니다:
func logf(level string, format string, args ...any)
s...로 슬라이스 넘기기
이미 슬라이스가 있다면 그대로 넘길 수는 없습니다. sum(nums)는 cannot use nums (variable of type []int) as int value in argument to sum으로 실패합니다. 인자 뒤에 ...을 붙여 펼치세요:
append도 가변 인자 함수라서, 두 슬라이스를 이어 붙일 때 append(a, b...)라고 씁니다.
펼치기는 가변 인자 자리에 혼자 있어야 합니다. sum(1, nums...)는 too many arguments in call to sum으로 실패합니다. append로 합친 슬라이스를 먼저 만드세요.
슬라이스는 복사되지 않고 공유된다
개별 인자로 호출하면 Go가 새 슬라이스를 만들어 담습니다. 기존 슬라이스를 s...로 펼치면 함수는 바로 그 슬라이스를 받습니다. 요소에 쓰면 호출한 쪽의 데이터가 바뀝니다.
출력:
[7 8 9]
[0 8 9]
매개변수를 수정하는 가변 인자 함수라면 그 사실을 문서에 적거나 slices.Clone으로 먼저 복사해야 합니다. 대부분의 가변 인자 함수는 인자를 읽기만 하므로 문제가 되는 일은 드물지만, 나머지 매개변수(rest parameter)가 항상 새 배열인 언어와는 분명히 다릅니다.
...any와 fmt.Println의 동작
fmt.Println은 func Println(a ...any) (n int, err error)로 선언되어 있습니다. any(Go 1.18부터 interface{}의 별칭)는 모든 타입을 받으므로, 문자열과 숫자와 구조체를 한 번에 넘길 수 있습니다.
중요한 부분은 전달입니다. logf는 args...를 Printf로 넘깁니다. 점을 빼면 Printf는 []any 하나를 인자로 받고, 경고 줄은 [WARN] [3 8] of %!d(MISSING) workers idle로 출력됩니다. go vet은 fmt.Printf(format, args)처럼 형식 매개변수가 그대로 전달될 때만 missing ... in args forwarded to printf-like function을 보고합니다. 이 예제에서는 형식을 +로 만들기 때문에 vet이 아무 말도 하지 않으니, 점은 직접 챙겨야 합니다.
[]string은 ...any로 펼칠 수 없습니다. 요소 타입이 정확히 같아야 하므로 먼저 변환합니다:
names := []string{"a", "b"}
args := make([]any, len(names))
for i, n := range names {
args[i] = n
}
fmt.Println(args...)
인자를 최소 하나 요구하기
가변 매개변수는 인자 0개도 받습니다. 함수에 인자가 최소 하나 필요하다면 첫 번째를 일반 매개변수로 만드세요:
이제 maxOf()는 런타임 검사가 아니라 컴파일 오류가 됩니다. 내장 함수 max도 같은 방식으로 동작하며, 인자를 최소 하나 요구합니다.
선택적 인자로 쓰는 가변 매개변수
Go에는 매개변수 기본값이 없습니다. 선택적 인자 하나를 흉내 내려고 가변 매개변수를 쓰기도 합니다:
func connect(addr string, timeout ...time.Duration)
동작은 하지만 읽기 좋지 않습니다. 호출하는 쪽에서 타임아웃을 세 개 넘길 수 있는데, 시그니처만 봐서는 어느 값이 쓰이는지 알 수 없습니다. 옵션이 둘 이상이면 설정 구조체나 함수형 옵션 패턴(...Option, 각 Option은 필드 하나를 설정하는 함수)을 쓰세요. 이 패턴도 가변 인자이지만 인자마다 의미가 분명합니다.
규칙 정리
| 규칙 | 예시 |
|---|---|
| 마지막 매개변수만 가변일 수 있음 | func f(a string, b ...int) |
| 함수 안에서 매개변수는 슬라이스 | b의 타입은 []int |
| 인자 0개면 nil 슬라이스 | f("x")이면 b == nil |
...으로 슬라이스 펼치기 | f("x", nums...) |
| 펼치면 기반 배열을 공유 | b[i]에 쓰면 nums가 바뀜 |
| 펼치기와 추가 값은 섞을 수 없음 | f("x", 1, nums...)는 컴파일되지 않음 |
자주 묻는 질문
Go에서 가변 인자 함수란 무엇인가요?
func sum(nums ...int) int처럼 마지막 매개변수가 ...T 형태인 함수입니다. T 타입 인자를 0개 이상 넘겨 호출할 수 있고, 함수 안에서 이 매개변수는 []T 슬라이스입니다.
Go에서 가변 인자 함수에 슬라이스를 넘기려면 어떻게 하나요?
슬라이스 뒤에 ...을 붙입니다: sum(nums...). 슬라이스는 복사되지 않고 그대로 전달되므로 함수는 같은 기반 배열을 봅니다. 펼치기와 개별 인자는 섞을 수 없습니다: sum(1, nums...)는 컴파일되지 않습니다.
Go에 스프레드 연산자가 있나요?
가변 인자 호출의 마지막 인자에만 있습니다: f(s...). 배열이나 구조체를 만들 때, 또는 가변 인자가 아닌 함수를 슬라이스로 호출할 때 쓰는 범용 스프레드는 없습니다. append(a, b...)는 같은 규칙을 내장 함수 append에 적용한 것입니다.
Go 함수에 가변 매개변수를 두 개 둘 수 있나요?
없습니다. 마지막 매개변수만 가변일 수 있고, 함수당 하나뿐입니다. 추가 목록은 일반 슬라이스 매개변수로 넘기세요.