길이와 용량
길이와 용량 (Length and capacity)
슬라이스나 맵, 채널을 쓸 때면 빠지지 않고 등장하는 게 len과 cap이에요. 이 둘이 정확히 어떤 타입에서 뭘 돌려주는지, 또 언제 상수(constant)로 처리되는지 짚고 넘어갈게요.
출처: Go Specification
본문
내장 함수 len과 cap 은 여러 타입의 인자를 받아서 int 타입의 결과를 돌려줘요. 구현체는 이 결과가 항상 int에 들어맞는 크기임을 보장해요.
Call Argument type Result
len(s) string type string length in bytes
[n]T, *[n]T array length (== n)
[]T slice length
map[K]T map length (number of defined keys)
chan T number of elements queued in channel buffer
type parameter see below
cap(s) [n]T, *[n]T array length (== n)
[]T slice capacity
chan T channel buffer capacity
type parameter see below
만약 인자의 타입이 [Type parameter declarations](type parameter) P라면, len(e)(또는 그에 대응하는 cap(e)) 호출이 P의 타입 집합(type set)에 속한 각각의 타입에 대해 유효해야 해요. 결과는 P가 Instantiations될 때 함께 쓰인 타입 인자에 해당하는 타입을 가진 인자의 길이(또는 각각 용량)가 되는 거예요.
슬라이스의 용량(capacity)은 그 슬라이스가 자리 잡고 있는 기반 배열(underlying array)에 실제로 할당된 공간의 원소 수를 뜻해요. 그래서 어느 시점에든 다음 관계가 항상 성립해요:
0 <= len(s) <= cap(s)
nil 슬라이스나 nil 맵, nil 채널의 길이는 0이에요. nil 슬라이스나 nil 채널의 용량도 0이죠.
한 가지 헷갈리기 쉬운 부분이 있어요. len(s) 표현식은 s가 문자열 상수(string constant)일 때 상수예요. 또 s의 타입이 배열이거나 배열을 가리키는 포인터이고, 표현식 s가 [Receive operator](channel receives)나 (상수가 아닌) [Calls](function calls)를 포함하지 않는다면 len(s)와 cap(s) 모두 상수가 돼요 — 이 경우엔 s는 아예 평가(evaluate)되지 않아요. 그 외의 경우에는 len과 cap 호출은 상수가 아니고 s가 평가돼요.
아래 예시는 상수와 상수가 아닌 호출을 나란히 두고 비교해 둔 건데요. 주석만 따라가 봐도 각 줄이 왜 유효한지(혹은 왜 안 되는지) 바로 읽혀요.
const (
c1 = imag(2i) // imag(2i) = 2.0 is a constant
c2 = len([10]float64{2}) // [10]float64{2} contains no function calls
c3 = len([10]float64{c1}) // [10]float64{c1} contains no function calls
c4 = len([10]float64{imag(2i)}) // imag(2i) is a constant and no function call is issued
c5 = len([10]float64{imag(z)}) // invalid: imag(z) is a (non-constant) function call
)
var z complex128
c5가 유효하지 않은 이유가 딱 궁금한 지점이죠. imag(z)는 z가 (상수가 아닌) 변수라서 함수 호출로 취급되거든요. 그래서 배열 안에 함수 호출이 들어가 버려서 len이 상수가 될 수 없어요.
더 알아보기
- 슬라이스를 만들고 그 기반 배열과 용량이 정해지는 과정은 Making slices, maps and channels 섹션에서 이어서 볼 수 있어요.
- 배열과 포인터, 슬라이스 타입의 정의는 Array types, Slice types 섹션에서 확인해요.