복합 리터럴
복합 리터럴 (Composite literals)
구조체(struct), 배열(array), 슬라이스(slice), 맵(map)의 새 값을 만들 때 매번 리터럴을 적어 주는 방식이 있어요. 바로 복합 리터럴인데요, 어떤 타입인지 그 뒤에 중괄호로 묶은 요소 목록을 붙이는 방식이에요. 요소마다 앞에 키(key)를 함께 적을 수도 있죠. 이번 절에서는 그 문법과 세부 규칙을 하나씩 살펴볼게요.
출처: Go Specification
본문
복합 리터럴은 평가될 때마다 구조체, 배열, 슬라이스, 맵의 새 값을 만듭니다. 리터럴의 타입 다음에 (비어 있을 수도 있는) 중괄호로 묶인 요소 목록이 따라옵니다. 각 요소 앞에는 해당하는 키를 선택적으로 붙일 수 있습니다.
CompositeLit = LiteralType LiteralValue .
LiteralType = StructType | ArrayType | "[" "..." "]" ElementType |
SliceType | MapType | TypeName [ TypeArgs ] .
LiteralValue = "{" [ ElementList [ "," ] ] "}" .
ElementList = KeyedElement { "," KeyedElement } .
KeyedElement = [ Key ":" ] Element .
Key = FieldName | Expression | LiteralValue .
FieldName = identifier .
Element = Expression | LiteralValue .
LiteralType가 타입 매개변수(type parameter)가 아니라면, 그 underlying type은 구조체, 배열, 슬라이스, 맵 타입이어야 합니다. 이 제약은 문법 수준에서 강제되는데, 타입이 TypeName으로 주어진 경우만 예외가 돼요. LiteralType이 타입 매개변수라면 그 타입 집합의 모든 타입이 같은 underlying type을 가져야 하고, 그 타입이 유효한 복합 리터럴 타입이어야 합니다.
요소와 키의 타입은 각각 LiteralType의 해당 필드·요소·키 타입에 할당 가능(assignable)해야 하며, 추가 변환은 일어나지 않아요. 키는 구조체 리터럴에서는 필드 셀렉터(selector)로, 배열·슬라이스 리터럴에서는 인덱스로, 맵 리터럴에서는 맵 키로 해석됩니다. 같은 필드 셀렉터나 상수 키 값으로 여러 요소를 지정하면 오류가 되고요. 요소 목록은 빼도 되는데, 그런 리터럴은 해당 타입의 zero value로 평가됩니다.
한 가지 헷갈리기 쉬운 구문 모호성이 있어요. LiteralType의 TypeName 형태를 쓰는 복합 리터럴이 if·for·switch 문의 블록을 여는 중괄호와 키워드(keyword) 사이에 피연산자로 나타나고, 그 리터럴이 괄호·대괄호·중괄호로 둘러싸여 있지 않은 경우인데요. 이런 드문 경우에 리터럴의 여는 중괄호가 문(statement)의 블록을 시작하는 중괄호로 잘못 파싱됩니다. 이 모호함을 해결하려면 복합 리터럴을 괄호 안에 넣어 주면 돼요.
if x == (T{a,b,c}[i]) { … }
if (x == T{a,b,c}[i]) { … }
구조체 리터럴 (Struct literals)
키가 없는 구조체 리터럴에서 요소 목록은 필드가 선언된 순서대로 각 구조체 필드에 하나씩 요소를 담아야 합니다.
키가 있는 구조체 리터럴에는 다음 규칙이 적용됩니다.
- 모든 요소는 키를 가져야 합니다.
- 각 키는 구조체의 (승격될 수 있는, possibly promoted) 필드에 대한 유효한 필드 셀렉터(selector)여야 하며(Go 1.27), 그 키가 그 필드를 선택합니다.
- 선택된 필드에 도달하기 위해 지나가는 임베디드 필드(있다면)의 타입은 포인터 타입이 아니어야 합니다.
- 임베디드 구조체 안의 승격 필드를 키로 가리킬 수는 없는데, 그 구조체가 다른 키로도 지정되는 경우죠.
- 요소 목록에 모든 구조체 필드의 요소가 다 있을 필요는 없어요. 빠진 필드는 그 필드의 zero value를 갖게 됩니다.
선언이 다음과 같이 주어졌다고 해볼게요.
type Object struct { name, color string }
type Point3D struct { Object; x, y, z float64 }
type Line struct { Object; p, q Point3D }
이제 다음과 같이 쓸 수 있어요.
origin := Point3D{} // zero value for Point3D
line1 := Line{Object{}, origin, Point3D{y: -4, z: 12.3}} // zero value for line1.q.x
line2 := Line{name: "diagonal", q: Point3D{1, 1, 1}} // zero value for line2.Object.color, line2.p
하지만 필드 셀렉터는 겹치는 필드를 가리킬 수 없어요.
obj := Object{"edge", "black"}
line3 := Line{Object: obj, name: "diagonal"} // invalid: name denotes a field inside Object
배열·슬라이스 리터럴 (Array and slice literals)
배열·슬라이스 리터럴에는 다음 규칙이 적용됩니다.
- 각 요소는 배열에서 자신의 위치를 나타내는 정수 인덱스를 갖습니다.
- 키가 있는 요소는 그 키를 인덱스로 사용해요. 키는
int타입 값으로 표현 가능(representable)한 음이 아닌 상수여야 하고, 타입이 있는 상수라면 정수 타입이어야 합니다. - 키가 없는 요소는 이전 요소의 인덱스에 1을 더한 값을 씁니다. 첫 요소에 키가 없다면 그 인덱스는 0이에요.
복합 리터럴의 주소를 취하면 리터럴의 값으로 초기화된 고유한 변수를 가리키는 포인터가 생성됩니다.
var pointer *Point3D = &Point3D{y: 1000}
참고로 슬라이스나 맵 타입의 zero value는 같은 타입의 초기화된 빈 값과 같지 않아요. 따라서 빈 슬라이스·맵 복합 리터럴의 주소를 취하는 것은 new로 새 슬라이스·맵 값을 할당하는 것과 같은 효과가 아닙니다.
p1 := &[]int{} // p1 points to an initialized, empty slice with value []int{} and length 0
p2 := new([]int) // p2 points to an uninitialized slice with value nil and length 0
배열 리터럴의 길이는 리터럴 타입에 지정된 길이예요. 리터럴에 길이보다 적은 요소가 주어지면 빠진 요소는 배열 요소 타입의 zero value로 설정됩니다. 배열의 인덱스 범위를 벗어나는 인덱스 값을 가진 요소를 제공하는 것은 오류이고요. ... 표기는 최대 요소 인덱스에 1을 더한 값과 같은 배열 길이를 지정합니다.
buffer := [10]string{} // len(buffer) == 10
intSet := [6]int{1, 2, 3, 5} // len(intSet) == 6
days := [...]string{"Sat", "Sun"} // len(days) == 2
슬라이스 리터럴은 밑바탕이 되는 배열 리터럴 전체를 나타냅니다. 그래서 슬라이스 리터럴의 길이와 용량은 최대 요소 인덱스에 1을 더한 값이에요. 슬라이스 리터럴의 형태는
[]T{x1, x2, … xn}
이고, 배열에 적용한 슬라이스 연산의 축약형입니다.
tmp := [n]T{x1, x2, … xn}
tmp[0 : n]
맵 리터럴 (Map literals)
맵 리터럴에서 각 요소는 키를 가져야 합니다. 상수가 아닌 맵 키에 대해서는 평가 순서(evaluation order) 절을 참고하세요.
요소 타입 생략 (Elision of element types)
배열·슬라이스·맵 타입 T의 복합 리터럴 안에서, 그 자체가 복합 리터럴인 요소나 맵 키는 그것이 T의 요소·키 타입과 동일하면 해당 리터럴 타입을 생략할 수 있어요. 마찬가지로 복합 리터럴의 주소인 요소나 키는 요소·키 타입이 *T일 때 &T를 생략할 수 있고요.
[...]Point{{1.5, -3.5}, {0, 0}} // same as [...]Point{Point{1.5, -3.5}, Point{0, 0}}
[][]int{{1, 2, 3}, {4, 5}} // same as [][]int{[]int{1, 2, 3}, []int{4, 5}}
[][]Point{{{0, 1}, {1, 2}}} // same as [][]Point{[]Point{Point{0, 1}, Point{1, 2}}}
map[string]Point{"orig": {0, 0}} // same as map[string]Point{"orig": Point{0, 0}}
map[Point]string{{0, 0}: "orig"} // same as map[Point]string{Point{0, 0}: "orig"}
type PPoint *Point
[2]*Point{{1.5, -3.5}, {}} // same as [2]*Point{&Point{1.5, -3.5}, &Point{}}
[2]PPoint{{1.5, -3.5}, {}} // same as [2]PPoint{PPoint(&Point{1.5, -3.5}), PPoint(&Point{})}
유효한 배열·슬라이스·맵 리터럴의 예시를 볼게요.
// list of prime numbers
primes := []int{2, 3, 5, 7, 9, 2147483647}
// vowels[ch] is true if ch is a vowel
vowels := [128]bool{'a': true, 'e': true, 'i': true, 'o': true, 'u': true, 'y': true}
// the array [10]float32{-1, 0, 0, 0, -0.1, -0.1, 0, 0, 0, -1}
filter := [10]float32{-1, 4: -0.1, -0.1, 9: -1}
// frequencies in Hz for equal-tempered scale (A4 = 440Hz)
noteFrequency := map[string]float32{
"C0": 16.35, "D0": 18.35, "E0": 20.60, "F0": 21.83,
"G0": 24.50, "A0": 27.50, "B0": 30.87,
}
더 알아보기
- Composite literal types — 리터럴이 만들 수 있는 타입의 범위를 함께 보면 좋아요.
- Struct types — 구조체 리터럴의 밑바탕이 되는 필드 선언과 임베딩 규칙.
- Array types / Slice types — 배열·슬라이스 리터럴의 요소 인덱스 규칙과 연결됩니다.
- Map types — 맵 리터럴의 키 규칙과 연결됩니다.
- Order of evaluation — 상수가 아닌 맵 키의 평가 순서.