Await 표현식
Await 표현식 (.await)
비동기 코드를 작성하다 보면 "이 작업이 끝날 때까지 잠시 멈춰 있다가 결과를 받아오고 싶다"는 순간이 오는데, 그때 쓰는 게 await 표현식이에요. 어떤 값이 준비될 때까지 계산을 일시 중단(suspend) 했다가, 준비되면 다시 진행하는 구문이에요.
본문
await 표현식은 std::future::IntoFuture를 구현한 대상이 값이 생길 준비가 될 때까지 계산을 중단시키는 구문적 장치예요.
구문 (Syntax)
AwaitExpression → Expression . await
구조를 보면, 먼저 IntoFuture 트레이트를 구현하는 타입을 가진 표현식(future operand) 이 오고, 그다음 . 토큰, 마지막에 await 키워드가 와요.
let value = some_future.await;
await 표현식은 비동기 컨텍스트(async context) 안에서만 쓸 수 있어요. 예를 들어 async fn, async 클로저, async 블록 같은 곳이에요. 그 밖의 일반 함수에서는 쓸 수 없어요.
수행되는 일
await 표현식이 평가되면 실제로 이런 일이 순서대로 벌어져요.
- future 피연산자에
IntoFuture::into_future를 호출해서 future를 만든다. - future를
tmp라는 임시 값으로 평가한다. Pin::new_unchecked로tmp를 핀(pin) 한다.- 핀된 future를 현재 task 컨텍스트를 넘기며
Future::poll메서드로 폴링한다. - 만약
poll이Poll::Pending을 돌려주면, future도Poll::Pending을 돌려주며 상태를 중단시킨다. 그래서 주변 async 컨텍스트가 다시 폴링될 때 3번 단계부터 실행을 재개한다. - 그 외의 경우
poll은Poll::Ready를 돌려줬어야 하며, 그 안에 담긴 값이await표현식 자체의 결과가 된다.
핵심은, await는 단순히 "기다렸다가 쓰는" 게 아니라 미래의 어느 시점에 폴링을 재개하는 루틴이라는 거예요. 이벤트 루프가 다시 돌아왔을 때 중단됐던 지점부터 이어서 진행하는 구조예요.
Task 컨텍스트
task 컨텍스트는 "현재 async 컨텍스트가 폴링될 때 공급된 Context"를 말해요. await 표현식은 async 컨텍스트에서만 허용되므로, 그만큼 항상 사용 가능한 task 컨텍스트가 존재해요.
근사적인 desugaring
실은 await 표현식은 훨씬 단순한 형태로 "펼쳐져(desugar) 있다"고 봐도 돼요. 다음은 그 근사적인 desugaring이에요 (표준 비규범 예시).
match operand.into_future() {
mut pinned => loop {
let mut pin = unsafe { Pin::new_unchecked(&mut pinned) };
match Pin::future::poll(Pin::borrow(&mut pin), &mut current_context) {
Poll::Ready(r) => break r,
Poll::Pending => yield Poll::Pending,
}
}
}
여기서 yield 의사 코드는 Poll::Pending을 돌려주고, 다시 호출되면 그 지점부터 실행을 재개해요. current_context 변수는 async 환경에서 가져온 컨텍스트를 가리켜요.
에디션 차이
await 표현식은 Rust 2018 에디션부터 사용할 수 있어요. 2015 에디션에서는 쓸 수 없고요.