rokdocs
v0.1

4.8. 트레일링 클로저

함수나 메서드 호출의 마지막 인자가 클로저일 때, 그 클로저를 괄호 밖으로 빼서 더 자연스러운 형태로 적을 수 있습니다. 이렇게 빼낸 형태를 트레일링 클로저라고 부릅니다.

4.8.1. 형태

트레일링 클로저의 문법은 다음과 같이 정의됩니다.

trailing_call   ::= primary "(" arg_list? ")" trailing_closure
                  | primary trailing_closure                    // 0-arg call
trailing_closure ::= ("|" closure_params? "|" ("->" type)?)? block
함수(인자들) |매개변수| { 본체 }
함수(인자들) { 본체 }            // 0-매개변수 단축

클로저는 호출 괄호 직후에 위치하며, 본체는 블록 표현식 의 블록입니다.

0-매개변수 단축: { body }

매개변수가 없는 클로저의 경우 || { body } 대신 { body } 형태로 단축해서 적을 수 있습니다.

let h = thread::spawn { heavy_work() };
// == thread::spawn(|| { heavy_work() })

with_lock(mutex) { critical_section() };

이 단축은 함수 호출 직후의 { ... } 에서만 의미가 있습니다. 다른 자리에 등장하는 { ... } 는 블록 표현식 의 일반 블록 표현식으로 해석됩니다.

1+ 매개변수

매개변수가 하나 이상인 경우에는 다음과 같이 적습니다.

let doubled = xs.map |x| { x * 2 };
let pos = xs.filter |x| { x > 0 };
let opened = with_file(path) |f| {
    let data = f.read();
    process(data)
};
pairs.for_each |k, v| { print(k, v) };

파이프 (|...|) 는 괄호 밖, 본체 직전에 위치합니다. 본체는 항상 블록이어서 |x| body 같은 한 줄 형태가 아니라 언제나 |x| { body } 형태로 적습니다.

트레일링 클로저는 가독성과 일관성을 위해 블록 본체를 기본으로 삼고 있습니다.

괄호 없는 형태의 규칙

xs.map |x| { .. } 처럼 호출 괄호 없이 클로저를 붙이는 형태는 다음 조건에서만 트레일링 클로저입니다.

  • 바로 앞이 경로 (thread::spawn, f) 이거나 멤버 이름 (.map) 입니다.
  • 뒤에 |매개변수|, 선택적 -> R, 그리고 { 가 이어집니다. 이 모양이 아니면 | 는 비트 OR 입니다.
  • || 는 언제나 논리 OR 입니다. 매개변수 없는 클로저는 { body } 단축으로 씁니다.
  • if / while / match 의 머리 (조건과 scrutinee) 안에서는 괄호 없는 트레일링 클로저와 { body } 단축이 꺼집니다. 그 자리의 { 는 본체 블록을 엽니다. 머리에서 클로저가 필요하면 괄호로 씁니다 (if xs.any(|x| { x > 0 }) { .. }).
let ys = xs.map |x| -> i64 { x as i64 * 2 };   // 반환 타입 명시
let z = a | b;                                  // 비트 OR - 뒤에 `|params| {` 모양이 아님
if ok || retry { .. }                           // 논리 OR

4.8.2. 명시 괄호와의 자유 선택

트레일링 클로저는 허용일 뿐 강제가 아닙니다.

xs.map(|x| x * 2);                // 명시 괄호 - OK
xs.map |x| { x * 2 };              // 트레일링 - OK

따라서 팀이나 사용자 스타일에 따라 자유롭게 골라 쓸 수 있습니다.

4.8.3. 함수 호출의 마지막 인자가 아닌 경우

트레일링 클로저는 마지막 인자 자리만 채울 수 있습니다. 클로저가 중간 인자라면 명시 괄호를 사용해야 합니다.

fn process(input: str, transform: fn(str) -> str, output: str) -> str { ... }

process("in", |s| { s.to_upper() }, "out");           // 명시 괄호 (중간 인자)
process("in", "out") |s| { s.to_upper() };            // ERROR - 트레일링은 마지막 자리만

4.8.4. 인자 0 개 호출

호출에 인자가 없을 때는 빈 괄호도 생략할 수 있습니다.

thread::spawn { heavy() };        // thread::spawn() { heavy() } 와 동일
                                   // == thread::spawn(|| { heavy() })

thread::spawn 처럼 매개변수가 클로저 한 개라면 위 형태가 가장 자연스러운 호출 방식이 됩니다.

4.8.5. 중첩

트레일링 클로저는 자유롭게 중첩할 수 있습니다.

par::scope |s| {
    s.spawn { f() };
    s.spawn { g() };
};

내부 클로저에도 같은 단축 규칙 ({ body } ↔ || { body }) 을 그대로 적용할 수 있습니다.

4.8.6. 파서 모호성 처리

트레일링 클로저를 도입하면 일부 코드가 블록 표현식 또는 트레일링 클로저로 해석될 수 있는 모호함이 생깁니다. 다음 규칙으로 이 모호함을 해소합니다.

위치의미
제어 키워드 (if, else, for, while, loop, match) 뒤 { ... }제어 본체
함수 호출 직후 { ... }트레일링 클로저 (0-arg)
함수 호출 직후 |params| { ... }트레일링 클로저 (1+ arg)
x = { ... }; 같은 일반 표현식 자리 { ... }블록 표현식 (즉시 평가) - 0-arg 클로저는 명시적 || { ... }
식 안 |...| { ... }일반 클로저 식

핵심 규칙을 요약하면, 함수 호출 직후 ((args)) 에 공백이 있든 없든 { ... } 또는 |...| { ... } 가 따라오면 트레일링 클로저로 해석됩니다.

4.8.7. {} 단독의 일반 블록과 0-arg 클로저

let x = { 42 };                   // 블록 표현식 (값 42)
let f = { 42 };                   // ??

위 두 줄은 문맥상 같은 형태인데, 어떤 의미가 우선이 될까요.

규칙은 이렇습니다. { body } 가 식 자리에 단독으로 등장하면 블록 표현식으로 해석합니다.

만약 0-arg 클로저를 의도한다면 명시적으로 || { body } 를 적어야 합니다.

let f = || { 42 };                // 명시 0-arg 클로저
let r = f();                       // 호출

즉, 함수 호출 직후의 { body } 만 트레일링 클로저로 인식됩니다.

4.8.8. par for 같은 문 자리의 클로저

병렬 루프, 트랜잭션, 컨텍스트 매니저 같은 자리에서 트레일링 클로저는 키워드처럼 읽히는 DSL 친화적인 형태를 만들어 줍니다.

par::for_each(xs) |x| { ... }     // par for 처럼 보임
with_lock(mutex) { ... }           // with-statement 처럼 보임
retry(3) { ... }                   // retry 키워드처럼 보임

이는 키워드 의 예약 키워드 최소화 원칙과도 일관됩니다. 라이브러리 함수와 트레일링 클로저를 결합하면 제어 흐름처럼 읽히는 패턴을 만들 수 있습니다.

4.8.9. 이름 있는 인자와의 결합

fn run(name: str = "task", body: fn() -> ()) { ... }

run("compute") { ... };           // 위치 + 트레일링 OK
run(name: "compute") { ... };     // named + 트레일링 OK

위치 인자와 named 인자, 그리고 트레일링 클로저는 자유롭게 결합할 수 있습니다.

4.8.10. 정리

트레일링 클로저는 사양 수준에서 추가 키워드 없이도 DSL 같은 표현을 가능하게 합니다. 사용자가 언어 키워드처럼 보이는 패턴을 라이브러리로 직접 만들 수 있다는 점이 핵심입니다.

GitHub에서 이 페이지 고치기