바인딩은 값에 이름을 붙입니다. 선언하는 순간 두 가지가 함께 정해집니다. 하나는 나중에 그 이름에 새 값을 대입할 수 있는지입니다. 다른 하나는 코드의 어느 범위에서 그 이름이 보이는지입니다. 클로저는 바깥 함수가 끝난 다음에도 필요한 바인딩을 살려 둘 수 있습니다.
캡처한 바인딩이 바뀌는 모습 보기
다음 코드를 bindings-scope-closures.tpz로 저장합니다.
function makeCounter(start: int) -> () -> int {
let mut current = start
() => {
current = current + 1
current
}
}
let label = "outer"
let next = makeCounter(10)
{
let label = "inner"
print("{label}: {next()}, {next()}")
}
print(label)검사한 다음 실행합니다.
topaz check bindings-scope-closures.tpz
topaz run bindings-scope-closures.tpz
두 번의 호출은 하나의 카운터를 공유합니다. 블록 안쪽의 label은 블록이 끝날 때 사라집니다.
inner: 11, 12
outer복사된 값이 아니라 바인딩 따라가기
makeCounter는 가변 바인딩 current를 만듭니다. 그다음 람다를 돌려줍니다. 람다가 current를 쓰므로 이 바인딩을 캡처합니다. 원래 선언이 let mut이라서 람다 안에서 새 값을 대입할 수 있습니다.
클로저는 처음 값 10을 복사해 두지 않습니다. 같은 가변 저장 공간 하나를 계속 참조합니다. 이 저장 공간을 셀이라고 부릅니다. 첫 호출은 셀을 11로 바꿉니다. 두 번째 호출은 그 값을 읽어 12로 바꿉니다. makeCounter가 끝난 다음에도 next가 이 셀을 참조합니다. 그래서 셀은 계속 살아 있습니다.
label은 렉시컬 스코프를 보여 줍니다. 안쪽 블록은 바깥 이름을 가리고 자기 바인딩을 만들 수 있습니다. 블록 안에서 label은 "inner"를 뜻합니다. 블록을 벗어나면 안쪽 바인딩의 스코프가 끝납니다. 그러면 바깥의 "outer"가 다시 보입니다. 같은 스코프에서 같은 이름을 두 번 선언하면 정적 오류입니다.
바인딩을 의도적으로 고르기
| 형태 | 사용할 때 | 재대입 |
|---|---|---|
const | 제한된 상수 표현식으로 계산되는 컴파일 시점 값 | 불가 |
let | 바인딩을 바꾸지 않는 일반적인 런타임 값 | 불가 |
let mut | 재대입 자체가 모델의 일부인 상태 | 가능 |
기본 선택은 let입니다. 나중에 하는 대입이 의미 있는 동작일 때만 mut를 붙이세요. 그러면 어떤 상태가 변하는지 선언 위치에서 바로 알 수 있습니다.
매개변수와 패턴이 만든 이름은 불변입니다. 함수 안에서 바뀌는 상태가 필요하면 매개변수 값을 새 let mut 바인딩에 담으세요. 그리고 그 상태를 잘 설명하는 이름을 붙이세요.
흔한 실수: 캡처를 스냅샷으로 생각하기
불변 바인딩을 캡처하면 클로저 안에서도 읽기만 됩니다. 가변 바인딩을 캡처하면 살아 있는 셀을 그대로 공유합니다. 그래서 한 클로저가 쓴 값을 같은 바인딩의 다음 읽기에서 볼 수 있습니다. 두 클로저가 가변 바인딩 하나를 캡처했다면 카운터가 따로 생기지 않습니다. 둘은 같은 셀을 씁니다.
클로저를 만드는 순간에도 스코프 규칙은 그대로 적용됩니다. 그 자리에서 보이는 이름은 캡처할 수 있습니다. 하지만 뒤에서 선언되는 이름이나 이웃 블록 안의 바인딩에는 접근할 수 없습니다.
정확한 한계
- 안쪽 스코프는 바깥 이름을 가릴 수 있습니다. 다만 한 스코프에서 같은 이름을 두 번 선언할 수는 없습니다.
- 불변
let,const, 매개변수, 패턴 바인딩에는 새 값을 대입할 수 없습니다. - 가변 선언에는 식별자 하나만 씁니다. 구조 분해 패턴은 쓸 수 없습니다.
- 명시적 캡처 목록, 이동 캡처 문법, Rust식 클로저 트레이트, 빌림 수신자 문법은 지원하지 않습니다.
- 리소스 수명은 별도의 선택입니다. File 전용
using과 결정적인 정리는 Defer와 리소스에서 다룹니다.
불변 바인딩과 가변 바인딩을 처음 만나는 자리는 값과 함수입니다. 이름 있는 함수와 람다, 제네릭 함수 가운데 무엇을 고를지는 함수와 제네릭에서 이어서 살펴보세요.