[Rust] 비동기 러스트: Pinning (번역)

원문
https://rust-lang.github.io/async-book/04_pinning/01_chapter.html

future들을 polling하기 위해서는, Pin이라는 특수한 타입으로 future를 고정할 필요가 있습니다.

만약 이전 섹션인 "Future와 Task의 실행"에서 Future 트레잇에 대한 설명을 읽었다면, Future::poll 메서드 정의에서 self: Pin<&mut Self>의 형태로 Pin이 사용된 것을 봤을 겁니다.

하지만 이게 뭘 의미하고, 뭘 위해 필요한 걸까요?


Pinning을 왜 하나요?

Pinning은 해당 객체가 절대 이동되지 않을 거란 것을 보장할 수 있게 해줍니다.

이것의 필요성을 이해하려면, async/await가 어떻게 동작하는지를 먼저 알아야 하는데요.

다음 코드를 봐봅시다.

**let fut_one = ***/ ... */; **
**let fut_two = ***/ ... */; **
**async move { **
**    fut_one.await; **
**    fut_two.await; **
**} **

저 속을 들여다보면, 이건 Future를 구현하는 익명 타입을 생성해서 다음과 같은 poll 메서드를 제공합니다.

// async { ... } 블럭으로 생성될 Future 타입.
**struct AsyncFuture **
**{ **
**    fut_one: FutOne, **
**    fut_two: FutTwo, **
**    state: State, **
**} **

**// async 블럭에 들어갈 수 있는 상태의 목록 **
**enum State **
**{ **
**    AwaitingFutOne, **
**    AwaitingFutTwo, **
**    Done, **
**} **

**impl Future for AsyncFuture **
**{ **
**    type Output = (); **
**    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> **
**    { **
**        loop **
**        { **
**            match self.state **
**            { **
**                State::AwaitingFutOne => match self.fut_one.poll(..) **
**                { **
**                    Poll::Ready(()) => self.state = State::AwaitingFutTwo, **
**                    Poll::Pending => return Poll::Pending, **
**                } **
**                State::AwaitingFutTwo => match self.fut_two.poll(..) **
**                { **
**                    Poll::Ready(()) => self.state = State::Done, **
**                    Poll::Pending => return Poll::Pending, **
**                } **
**                State::Done => return Poll::Ready(()), **
**            } **
**        } **
**    } **
**} **

poll이 처음 호출되면, fut_one을 poll할 겁니다.
그리고 fut_one가 완료되지 않았다면 AsyncFuture::poll이 반환될 겁니다.

그리고 poll로의 Future 호출은 이전의 것이 중단된 위치에서 계속될 거에요.
->Future calls to poll will pick up where the previous one left off.

이러한 과정은 future가 성공적으로 완료될 때까지 계속 반복됩니다.

그런데, 참조를 사용하는 async 블럭이 있다면 무슨 일이 일어날까요?
예를 들어보죠.

**async { **
**    let mut x = [0; 128]; **
**    let read_into_buf_fut = read_into_buf(&mut x); **
**    read_into_buf_fut.await; **
**    println!("{:?}", x); **
**} **

이건 어떤 구조체로 컴파일될까요?

**struct ReadIntoBuf<'a> **
**{ **
**    buf: &'a mut [u8], **// 아래의 x를 가리킴
**} **

**struct AsyncFuture **
**{ **
**    x: [u8; 128], **
**    read_into_buf_fut: ReadIntoBuf<'what_lifetime?>, **
**} **

여기에서 ReadIntoBuf future는 우리 구조체의 다른 필드 x에 대한 참조를 가집니다.
그런데, AsyncFuture가 이동된다면, x의 위치도 이동되겠죠? read_into_buf_fut.buf가 저장된 포인터도 무효화될 거고요.

future를 메모리의 특정 지점에 고정하면(Pinning) 이러한 문제를 방지할 수 있습니다. async 블럭 내부의 값에 대한 참조를 안전하게 만들 수 있는거죠.



Pinning을 어떻게 사용하나요?

Pin 타입은 포인터 타입의 래퍼(wraps) 타입으로, 해당 포인터 뒤에 있는 값이 이동되지 않을 것임을 보장합니다.
가령 Pin<&mut T>, Pin<&T>, Pin<Box> 등은 전부 T가 이동되지 않을 것이 보장됩니다.

대부분의 타입은 이동에 대한 문제를 가지지 않는데요. 이러한 타입들은 Unpin 트레잇을 구현합니다.
Unpin 타입에 대한 포인터들은 자유롭게 Pin에 넣거나 빼낼 수 있습니다.
예를 들어, u8는 Unpin입니다. 그래서 Pin<&mut u8>은 일반적인 &mut u8처럼 동작할 거에요.

일부 함수들은 future가 Unpin으로 동작하길 요구할 수도 있습니다.

Unpin 타입을 필요로 하는 함수에 Unpin이 아닌 Future나 Stream을 쓰려면. 먼저 Box::pin이나 pin_utils::pin_mut! 매크로를 통해 값을 고정해야 합니다. Box를 쓸 경우엔 Pin<Box>를, 매크로를 쓸 경우엔 Pin<&mut T>를 생성하게 될 거에요.

Pin<Box>와 Pin<&mut Fut>는 둘다 future로 사용될 수 있고, 동시에 Unpin을 구현하게 됩니다. 

아래는 그 예시입니다.

**use pin_utils::pin_mut; **// pin_utils는 crate.io에 있는 유용한 크레이트입니다.

**// Unpin을 구현하는 Future를 받는 함수. **
**fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) **
*{ **
**   **** /
... */ **
**} **

**let fut = async { / ... / }; execute_unpin_future(fut); ****// Error: fut이 Unpin 트레잇을 구현하지 않음 **

**// Box로 Pinning: **
**let fut = async { / ... / }; **
**let fut = Box::pin(fut); execute_unpin_future(fut); ****// OK **

**// pin_mut!으로 Pinning: **
**let fut = async { / ... / }; **
**pin_mut!(fut); **
**execute_unpin_future(fut); **// OK