바인딩은 값에 이름을 붙입니다. 선언하는 순간 두 가지도 함께 정해집니다. 나중에 그 이름에 새 값을 대입할 수 있는지, 그리고 코드의 어느 범위에서 이름이 보이는지입니다. 클로저는 바깥 함수가 끝난 뒤에도 필요한 바인딩을 살아 있게 만들 수 있습니다.
캡처한 바인딩이 바뀌는 모습 보기
다음 코드를 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와 리소스에서 다룹니다.
첫 불변·가변 바인딩은 값과 함수에서 다시 볼 수 있습니다. 이름 있는 함수, 람다, 제네릭 함수 중 무엇을 고를지는 함수와 제네릭으로 이어서 살펴보세요.