언어 기초

바인딩·스코프·클로저

let과 let mut을 고르고, 블록 안팎의 이름과 클로저를 호출할 때 바뀌는 카운터를 따라갑니다.

어떤 이름에 새 값을 대입할 수 있는지, 어디에서 그 이름을 쓸 수 있는지 정해 보세요. 반환받은 함수를 호출할 때마다 바뀌는 카운터도 출력으로 확인합니다.

첫 프로그램을 따라 토파즈를 설치하세요. let과 함수의 기본 사용법은 값과 함수에서 배울 수 있습니다.

카운터가 바뀌는 모습 보기

작업 폴더에 다음 코드를 bindings-scope-closures.tpz로 저장하세요.

TOPAZ
function 카운터만들기(시작: int) -> () -> int {
    let mut 현재 = 시작
    () => {
        현재 = 현재 + 1
        현재
    }
}

let 레이블 = "outer"
let 다음 = 카운터만들기(10)

{
    let 레이블 = "inner"
    print("{레이블}: {다음()}, {다음()}")
}

print(레이블)

같은 폴더에서 파일을 검사하고 실행하세요.

BASH
topaz check bindings-scope-closures.tpz
topaz run bindings-scope-closures.tpz

검사가 성공하면 종료 코드는 0입니다. 프로그램은 다음 두 줄을 출력합니다.

출력
inner: 11, 12
outer

두 번의 호출 따라가기

바인딩은 이름과 값을 연결합니다. let mut 현재 = 시작은 새 값을 대입할 수 있는 바인딩을 만듭니다. 카운터만들기(10)은 이 값을 10으로 시작하고 =>로 쓴 이름 없는 함수를 반환합니다. 이 함수를 람다라고 부릅니다.

람다는 바깥 함수의 현재에 계속 접근합니다. 이를 캡처라고 하며, 이런 접근을 유지하는 함수를 클로저라고 합니다. 카운터만들기가 반환한 뒤에도 다음에 이 클로저가 남습니다.

단계현재의 값반환값
카운터만들기(10)10클로저
첫 다음() 호출1111
둘째 다음() 호출1212

두 호출은 같은 바인딩을 사용합니다. 둘째 호출은 첫 호출이 남긴 11에 1을 더합니다. 캡처한 바인딩은 클로저가 사용하는 동안 유지됩니다. 두 클로저가 같은 가변 바인딩을 캡처해도 바뀐 값을 함께 봅니다.

블록에서 사용하는 이름 찾기

스코프는 이름을 사용할 수 있는 코드 범위입니다. 토파즈는 렉시컬 스코프를 사용합니다. 이름의 선언 위치와 둘러싼 블록에 따라 어떤 바인딩을 가리키는지 정합니다.

안쪽의 let 레이블은 그 블록 안에서 바깥 바인딩을 가립니다. 이를 이름 가리기라고 합니다. 첫 print는 "inner"를 읽습니다. 블록 밖의 print(레이블)은 "outer"를 읽습니다. 같은 스코프에서 같은 이름을 두 번 선언하면 정적 오류입니다.

바인딩의 변경 여부 고르기

형태사용할 때재대입
const허용된 상수 표현식으로 컴파일 시점에 계산할 값불가
let대입한 값을 유지할 이름불가
let mut카운터처럼 새 값을 대입할 상태가능

먼저 let으로 선언하고 재대입이 필요하면 mut를 붙이세요. 매개변수와 패턴으로 만든 이름은 불변입니다. 함수 안에서 매개변수의 값을 바꾸려면 새 let mut 바인딩에 담으세요. 예제의 카운터만들기도 시작을 이렇게 사용합니다.

현재의 선언에서 mut를 지우면 람다 안의 대입이 정적 오류가 됩니다. 카운터를 계속 사용하려면 bindings-scope-closures.tpz에서 let mut 현재로 되돌리세요. 캡처해도 원래 바인딩의 변경 가능 여부는 그대로입니다.

캡처와 선언 규칙

  • 클로저는 생성하는 위치에서 볼 수 있는 이름을 캡처합니다. 나중에 선언한 이름이나 다른 형제 블록 안의 이름은 사용할 수 없습니다.
  • 가변 선언에는 단일 식별자를 씁니다. 구조 분해 패턴으로 가변 바인딩을 만들 수 없습니다.
  • 불변 let, const, 매개변수, 패턴 바인딩에는 클로저 안에서도 새 값을 대입할 수 없습니다.
  • 명시적 캡처 목록, 이동 캡처 문법, Rust식 클로저 트레이트, 빌림 수신자 문법은 지원하지 않습니다.

캡처한 바인딩의 수명이 파일을 닫는 시점을 정하지는 않습니다. File에 사용하는 using과 정리 규칙은 Defer와 리소스에서 확인하세요. 이름 있는 함수, 람다, 제네릭 함수의 선택은 함수와 제네릭에서 이어집니다.