클로저 표현식

클로저 표현식

클로저 표현식(closure expression)은 람다 표현식(lambda expression) 또는 람다(lambda)라고도 불려요. 어떤 클로저 타입을 정의하고, 그 타입의 값을 만들어 내요.

출처: Rust Reference

본문

클로저 표현식의 문법은 이렇게 구성돼요. 선택적인 async 키워드, 선택적인 move 키워드, 그리고 파이프 기호(|)로 감싸고 클로저 매개변수(각각 뒤에 :와 타입이 올 수 있는 패턴들의 쉼표 구분 목록)가 옵니다. 그다음 선택적으로 ->반환 타입이 오고, 마지막으로 클로저 몸통 피연산자라고 부르는 표현식이 와요.

각 패턴 뒤에 오는 선택적 타입은 그 패턴에 대한 타입 명시(type annotation)예요. 반환 타입이 있으면 클로저 몸통은 반드시 블록이어야 해요.

클로저 표현식은 매개변수 목록을 그 뒤에 오는 표현식에 대응시키는 함수를 나타내요. let 바인딩과 마찬가지로 클로저 매개변수는 **반증 불가능 패턴(irrefutable pattern)**이에요. 타입 명시는 선택사항이라 없으면 문맥에서 추론돼요.

각 클로저 표현식은 유일하고 익명인 타입을 가져요. 또 중요한 차이가 하나 있는데, 클로저 표현식은 환경을 캡처할 수 있다는 점이에요. 일반 함수 정의는 이게 안 되죠.

move 키워드가 없으면 클로저는 환경에서 각 변수를 어떻게 캡처할지 스스로 추론해요. 기본적으로는 공유 참조로 캡처하기를 선호해서, 클로저 몸통에서 언급한 모든 외부 변수를 사실상 빌려오게 돼요. 필요하면 컴파일러가 가변 참조를 쓰거나, 값을 그 타입에 따라 환경에서 move 또는 copy 하도록 추론을 바꾸기도 해요.

move 키워드를 붙이면 클로저가 환경을 값 복사 또는 move로 캡처하도록 강제할 수 있어요. 클로저의 수명을 'static으로 만들고 싶을 때 자주 쓰는 방법이에요.

#![allow(unused)]
fn main() {
async fn takes_async_callback(f: impl AsyncFn(u64)) {
 f(0).await;
 f(1).await;
}

async fn example() {
 takes_async_callback(async |i| {
  core::future::ready(i).await;
  println!("done with {i}.");
 }).await;
}
}

클로저 트레이트 구현

클로저 타입이 어떤 트레이트를 구현하는지는 변수를 어떻게 캡처했는지, 캡처한 변수의 타입, 그리고 async 여부에 따라 달라져요. 클로저가 Fn, FnMut, FnOnce를 언제 어떻게 구현하는지는 호출 트레이트와 강제 변환(call traits and coercions) 장에서 다뤄요. 클로저 타입은 캡처된 모든 변수의 타입이 그 트레이트를 구현할 때 SendSync를 구현해요.

async 클로저

async 키워드가 붙은 클로저는 async fn과 비슷하게 비동기 작업을 나타내요. async 클로저를 호출해도 실제 작업은 수행하지 않아요. 대신 클로저 몸통의 계산에 해당하는 Future를 구현한 값으로 평가되어요.

#![allow(unused)]
fn main() {
async fn takes_async_callback(f: impl AsyncFn(u64)) {
 f(0).await;
 f(1).await;
}

async fn example() {
 takes_async_callback(async |i| {
  core::future::ready(i).await;
  println!("done with {i}.");
 }).await;
}
}

2018 에디션 차이점: async 클로저는 Rust 2018부터 사용할 수 있어요.

예시

이 예시에서는 고차 함수 인자를 받는 ten_times 함수를 정의하고, 클로저 표현식을 인자로 넘겨 호출해요. 첫 호출에서는 평범한 클로저를, 두 번째에서는 환경에서 값을 move 하는 클로저를 넘겨요.

#![allow(unused)]
fn main() {
fn ten_times<F>(f: F) where F: Fn(i32) {
 for index in 0..10 {
  f(index);
 }
}

ten_times(|j| println!("hello, {}", j));
// With type annotations
ten_times(|j: i32| -> () { println!("hello, {}", j) });

let word = "konnichiwa".to_owned();
ten_times(move |j| println!("{}, {}", word, j));
}

클로저 매개변수의 속성

클로저 매개변수에 붙는 속성은 일반 함수 매개변수와 같은 규칙과 제약을 따라요.

더 알아보기

  • Fn/FnMut/FnOnce 호출 트레이트와 함수 포인터로의 강제 변환을 함께 보면 클로저의 동작 원리가 더 명확해져요.
  • async/awaitFuture에 대해 더 살펴보면 async 클로저의 실제 쓰임이 드러나요.