언어 기초

바인딩·스코프·클로저

불변·가변 바인딩을 고르고, 렉시컬 스코프와 클로저가 공유하는 하나의 가변 셀을 이해합니다.

바인딩은 값에 이름을 붙입니다. 선언하는 순간 두 가지도 함께 정해집니다. 나중에 그 이름에 새 값을 대입할 수 있는지, 그리고 코드의 어느 범위에서 이름이 보이는지입니다. 클로저는 바깥 함수가 끝난 뒤에도 필요한 바인딩을 살아 있게 만들 수 있습니다.

캡처한 바인딩이 바뀌는 모습 보기

다음 코드를 bindings-scope-closures.tpz로 저장합니다.

TOPAZ
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)

검사한 뒤 실행합니다.

BASH
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와 리소스에서 다룹니다.

첫 불변·가변 바인딩은 값과 함수에서 다시 볼 수 있습니다. 이름 있는 함수, 람다, 제네릭 함수 중 무엇을 고를지는 함수와 제네릭으로 이어서 살펴보세요.