Go 언어에서 '...' (줄임표) 연산자 완벽 정리

Go 언어에서 세 개의 점으로 이루어진 줄임표 연산자는 다양한 상황에서 사용됩니다. 이 글에서는 이 연산자의 세 가지 주요 용법을 실제 코드와 함께 설명하고, 자주 마주칠 수 있는 함정들을 짚어보겠습니다.

1. 배열 선언 시 길이 자동 결정

배열 리터럴에서 길이를 명시하는 자리에 줄임표를 사용하면, 초기화되는 요소의 개수에 따라 배열의 크기가 자동으로 결정됩니다.

package main

import "fmt"

func main() {
    // 배열의 길이는 초기화 값 1, 2, 3에 의해 3으로 설정됨
    arr := [...]int{1, 2, 3}
    fmt.Printf("타입: %T, 길이: %d\n", arr, len(arr)) // [3]int, 3
}

위 예제에서 [...]int는 컴파일러가 요소 개수를 세어 [3]int 타입의 배열을 생성합니다.

2. 슬라이스 풀기 (Spread)

슬라이스 뒤에 줄임표를 붙이면 슬라이스의 요소들을 개별 인자로 풀어서 다른 함수나 연산에 전달할 수 있습니다. 주로 append 함수와 함께 사용됩니다.

package main

import "fmt"

func main() {
    var numbers []int
    data := []int{10, 20, 30}

    // data... 는 data 슬라이스의 모든 요소를 개별 인자로 펼침
    // 즉, append(numbers, 10, 20, 30) 과 동일
    numbers = append(numbers, data...)
    fmt.Println("결과:", numbers) // [10 20 30]

    var bytes []byte
    bytes = append(bytes, []byte("Go 언어")...)
    fmt.Println("문자열:", string(bytes)) // Go 언어
}

이 방식은 가변 인자 함수를 호출할 때도 유용하게 사용됩니다.

3. 가변 인자 함수 선언

함수의 마지막 매개변수 타입 앞에 줄임표를 붙이면, 해당 함수는 개수가 정해지지 않은 인자를 받을 수 있습니다. 함수 내부에서는 이 인자들이 슬라이스 형태로 처리됩니다.

package main

import "fmt"

// sum 함수는 임의의 개수 정수를 받아 합계를 반환
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

func main() {
    fmt.Println("합계:", sum(1, 2, 3))          // 6
    fmt.Println("합계:", sum(5, 10, 15, 20))   // 50
    fmt.Println("합계:", sum())                 // 0 (빈 호출 가능)
}

가변 인자 함수의 타입과 일반 슬라이스를 매개변수로 받는 함수의 타입은 서로 다르다는 점을 기억해야 합니다.

package main

import "fmt"

// 가변 인자 함수
func variadic(nums ...int) {
    fmt.Println("variadic 내부:", nums)
}

// 슬라이스를 받는 일반 함수
func sliceParam(nums []int) {
    fmt.Println("sliceParam 내부:", nums)
}

func main() {
    data := []int{1, 2, 3}

    variadic(data...) // 올바른 호출: 슬라이스를 풀어서 전달
    // variadic(data) // 오류: []int 타입을 ...int에 직접 전달 불가

    sliceParam(data)   // 올바른 호출: 슬라이스 자체를 전달
    // sliceParam(data...) // 오류: ...int 타입을 []int에 직접 전달 불가
}

주의할 점: interface{} 와의 상호작용

가변 인자로 ...interface{}를 사용할 때는 특히 주의가 필요합니다. interface{} 자체는 모든 타입을 담을 수 있지만, []interface{}[]int[]string과 완전히 다른 타입입니다.

오류가 발생하는 경우

package main

import "fmt"

func printArgs(args ...interface{}) {
    fmt.Println(args)
}

func main() {
    nums := []int{1, 2, 3}
    // printArgs(nums...) // 오류: []int 타입을 ...interface{} 타입에 전달 불가
    printArgs(nums)       // 올바름: nums 자체가 하나의 interface{} 인자로 전달됨
}

위 예제에서 nums...를 직접 전달하면 타입 불일치 오류가 발생합니다. []int 타입의 슬라이스를 []interface{} 타입의 슬라이스로 자동 변환할 수 없기 때문입니다.

올바른 해결 방법

package main

import "fmt"

func printArgs(args ...interface{}) {
    fmt.Println("args:", args)
}

func main() {
    var mixed []interface{}
    mixed = append(mixed, 1, "hello", 3.14)

    // interface{} 슬라이스는 ...interface{}에 풀어서 전달 가능
    printArgs(mixed...)

    // 또는 개별 인자로 전달
    printArgs(1, "hello", 3.14)
}

[]interface{} 타입의 슬라이스만이 ...interface{} 가변 인자에 풀어서 전달될 수 있습니다.

이러한 동작 방식을 이해하면 Go에서 줄임표 연산자를 더욱 안전하고 효과적으로 활용할 수 있습니다.

태그: go 배열 슬라이스 가변인자 Interface

6월 20일 00:21에 게시됨