rokdocs
v0.1

4.5. 클로저

클로저는 이름 없이 표현식 위치에 등장하는 함수 값입니다. 외부 환경의 변수를 캡처할 수 있어서, 주변 상태에 의존하는 작은 함수를 자연스럽게 작성할 수 있습니다.

4.5.1. 형태

closure_expr   ::= capture_list? closure_params return_type? closure_body
closure_params ::= "||"
                 | "|" closure_param_list "|"
closure_param_list ::= closure_param ("," closure_param)* ","?
closure_param ::= identifier (":" type_expr)?
closure_body   ::= expr | block
capture_list   ::= "[" capture_item ("," capture_item)* "]"

0-매개변수 형태인 || 는 단일 토큰이며 (구두점과 연산자 토큰 을 참고하세요), | 두 개로 분리되지 않습니다.

|매개변수들| 본체

매개변수는 좌우의 | 토큰 (구두점과 연산자 토큰) 으로 둘러쌉니다. 본체는 식이거나 블록입니다.

단일 식 본체

|x| x + 1
|x: i32| x * 2
|a, b| a + b

|...| 직후에 단일 식이 오며, 그 식의 값이 클로저의 반환값이 됩니다.

블록 본체

|x: i32| -> i32 {
    let y = x * 2;
    y + 1
}

블록 본체일 때는 반환 타입을 -> 로 명시할 수 있습니다 (생략하면 추론에 맡깁니다).

0-매개변수 형태

|| body
|| { body }

매개변수가 없을 때는 || (두 파이프를 연속으로) 를 사용합니다. 토큰화 측면에서는 구두점과 연산자 토큰 의 고정 토큰표에서 || 가 한 토큰으로 정의됩니다.

4.5.2. 매개변수의 타입

매개변수 타입은 명시할 수도 있고 생략할 수도 있습니다.

|x: i32| x * 2                    // 명시
|x| x * 2                          // 추론

추론은 클로저가 사용되는 문맥에서 결정됩니다. 예를 들어 함수 인자로 넘기는 자리의 함수 타입이나 변수 선언의 타입 등이 그 문맥에 해당합니다. 문맥이 없으면 이후의 호출이 매개변수 타입을 정합니다 (let f = |x| x * 2; f(4); 의 x: i32, 2.10.2).

let f: fn(i32) -> i32 = |x| x * 2;   // 문맥에서 x: i32 추론
list.map(|x| x * 2);                  // map 의 시그니처에서 추론

4.5.3. 반환 타입의 추론 / 명시

|x| x * 2                          // 추론
|x: i32| -> i32 { x * 2 }          // 명시 - 블록 본체일 때만

반환 타입 명시 (-> 타입) 는 블록 본체와 함께만 사용합니다. 단일 식 본체는 항상 추론에 의존합니다.

4.5.4. 캡처

클로저는 외부 스코프의 변수를 캡처할 수 있습니다.

let n = 10;
let f = |x| x + n;                // n 을 캡처
f(5);                              // 15

캡처의 의미론 (값 캡처를 기본으로 하고, [ref x] 같은 캡처 리스트를 사용하는 방식) 은 캡처 의미론 에서 자세히 정의합니다.

4.5.5. 클로저 vs 함수

측면fn 명명 함수| ... | 클로저
이름있음없음
정의 위치모듈이나 impl모든 표현식 위치
외부 변수 캡처불가가능
호출name(...)value(...)
타입fn(...) -> ...같음

두 가지 모두 같은 함수 타입으로 표현되며, 인터페이스 측면에서 서로 호환됩니다. 따라서 한 자리에 둘 다 적합합니다.

4.5.6. 클로저 값의 사용

// 변수 대입
let f = |x| x * 2;

// 함수 인자
list.map(|x| x * 2);

// 함수 반환
fn make_adder(n: i32) -> fn(i32) -> i32 {
    |x| x + n
}

// 컬렉션
let handlers: List<fn(i32) -> i32> = [|x| x + 1, |x| x - 1, |x| x * 2];

4.5.7. 호출

let f = |x| x * 2;
let r = f(5);                     // 일반 함수 호출과 동일 형태

f(args) 형태로 호출합니다. 호출 시에는 캡처된 환경과 인자가 함께 본체 평가에 사용됩니다.

4.5.8. 즉시 호출: IIFE

클로저를 정의 직후에 즉시 호출하는 형태도 가능합니다.

let r = (|x: i32| -> i32 { x * 2 })(5);     // 10

자주 쓰지는 않지만, 지역 스코프와 즉시 평가를 표현하는 한 가지 방법으로 사용할 수 있습니다. 대부분의 경우에는 블록 표현식 의 일반 블록이 더 자연스럽습니다.

4.5.9. 단일 식 vs 블록의 가독성

list.map(|x| x * 2)               // 단일 식 - 짧고 명확
list.map(|x| { let y = x * 2; y + 1 })   // 블록 - 다단계 처리

본체 길이에 따라 자연스러운 형태를 골라서 사용합니다. 사양에서는 두 형태 모두 정식으로 지원합니다.

4.5.10. 트레일링 클로저

함수 호출의 마지막 인자가 클로저일 때는, 괄호 밖으로 빼서 더 읽기 좋게 적을 수 있습니다. 자세한 내용은 트레일링 클로저 에서 정의합니다.

4.5.11. 특수 변형: async 와 yield 수식어

클로저에 async 나 yield 수식어를 붙이면 비동기 형태나 제너레이터 형태의 클로저를 만들 수 있습니다.

async || { ... }                  // Future 반환 클로저
yield || { ... }                  // Iterator 반환 클로저
async yield || { ... }            // Stream 반환 클로저

자세한 규칙은 제너레이터, 비동기, 스트림 에서 정의합니다.

4.5.12. 0-arg 클로저의 단축: { body }

특정 문맥에서는 { body } 가 0-매개변수 클로저로 인식되는 단축 형태가 있습니다. 이 단축은 트레일링 클로저와 결합해 자주 사용됩니다.

thread::spawn { heavy_work() }    // == thread::spawn(|| { heavy_work() })

이 단축의 정확한 규칙은 트레일링 클로저 에서 정의합니다.

GitHub에서 이 페이지 고치기