Go언어 '...' 자세히 알아보기

fmt.Printf 함수를 떠올려보자. 이 함수는, 첫번째 인자로 format string을 받고, 이후 자리는 첫번째 인자에서 등장한 각각의 변수를 넣어준다. 예를들어, "Hi, %s!" 를 format string으로 사용한다면 1개의 문자열 변수가 이후 주어저야하고, "Hi, %s! My name is %s. 의 경우에는 2개의 문자열 변수가 주어져야한다. 즉, 상황에 맞추어서 함수 호출 시 사용되는 파라미터의 갯수가 달라진다는 것이다. 일반적으로 우리는 이런 함수를 가변인자 함수(variadic function) 라고 부른다.

Golang에서는 가변인자 함수를 어떻게 만들 수 있을까? 이 때 우리가 사용하는 것이 ...이다. 사용방법은 아래와 같다.

func Printf(format string, a ...interface{}) (n int, err error) {
	return Fprintf(os.Stdout, format, a...)
}

위 코드는 fmt.Printf 함수의 정의부분이다. 주목해야 하는 부분은 a ...interface{} 이다. fmt.Printf에 첫번째로 format string을 넘기고, 그 이후에 등장하는 변수들은 모두 a라는 변수에 interface{} slice형태로 담기게 된다. 쉽게 말해, 파라미터로 format string 이후 1개의 값을 넘기면 길이 1짜리 interface{} slice이 되고, 3개의 값을 넘기면 길이 3짜리 interface{} slice이 된다는 뜻이다. 이는 slice기때문에 go에서 slice 사용하듯 사용하면 된다. 만약 가변 인자 부분에 값을 하나도 넘기지 않으면(e.g. fmt.Printf("hi!")) 가변 인자의 값은 nil이 된다.

위 함수 예제의 return 문을 보면 a... 형태를 볼 수 있다. 이는 a안에 있는 내용을 풀어서 사용한다는 의미이다. 이를 이용해서 slice에 담겨있는 값을 풀어서 가변인자로 넘겨줄 수 있다. 특히 a... 형태로 가변 함수를 호출할 경우, 함수의 가변 인자에 할당 가능한 파라미터라면 새 변수를 만들지 않는다.1 예를 들어,

func Greeting(prefix string, who ...string)
s := []string{"James", "Jasmine"}
Greeting("goodbye:", s...)

이 상황에서 Greeting 함수 안에서 s는 재사용된다.

이 재사용은 메모리 allocation을 줄일 수 있기에 효과적이지만, 조심을 해야 하는 상황이 있다. 바로 아래와 같은 상황이다.

package main

func billo(a ...interface{}) string {
	return a[0].(string)
}

func main() {
	s := []string{"hi", "hi2"}
	billo(s...)
}

이 상황에서, 이상적으로 이 함수 호출에는 문제가 없어야 할 것 같다. stringinterface{} 로 간주할 수 있기 때문에, s를 전개하여 파라미터로 전달하고, billo 함수가 인자를 받을 때 각 stringinterface{}로 처리하면 작동할 것 같다. 하지만, 이는 잘 작동하지 않는다. 바로 재사용 때문이다. 실행해보기

./prog.go:9:7: cannot use s (type []string) as type []interface {} in argument to billo

코드를 실행하면 위와 같은 에러를 볼 수 있다. 에러를 직역해보면 []interface{} 자리에 []string을 쓸 수 없어요 이다. 상황을 해석해보자. 우리가 기대했던 것 처럼 s의 모든 값이 풀어진 이후 billo함수의 인자로 넘어갈 때 모아지는 것이 아닌, 변수가 재사용된다. 정의된 s 변수가 재사용되어, a 자리에 []string이 들어간다. interface{}자리에 string이 호출되는 것이 아닌, []interface{} 자리에는 []string이 들어가는 것은 불가능하기에 에러가 나게 된다. 변수가 재사용 되기 때문에, 기존에 정의된 타입정의를 그대로 사용하여 생기는 문제로 보인다. 위의 상황을 해결하려면, s를 바로 사용하는 대신 []interface{} 타입의 slice을 새로 선언하여 s의 내용을 넣어주어서 사용하면 된다. 쉽게 실수할 수 있는 만큼, 주의가 필요 해 보인다.


추가로, ... 가 등장하는 사례를 소개한다. 바로 배열 정의에 사용하는 방법이다.

a := [...]string{"a", "b", "c"}

대괄호 사이에 ...를 사용하면 뒤에 나열된 인자들의 갯수에 맞는 배열을 만들어준다. 즉 len(a) = 3이다.


코드 여러부분에서 ...이 등장하니 만큼, 사용법을 정확히 이해한다면 유용하게 쓸 수 있을 것이다.

(https://yourbasic.org/golang/three-dots-ellipsis/)