Pinning

futures을 폴링하려면 Pin<T> 라는 특수 type을 사용하여 고정 해야 합니다. 이전 섹션 Future와 타스크의 실행에서 Future trait에 대한 설명을 읽으면 Future::poll 메소드 정의의 self: Pin<&mut Self>에서 Pin을 볼 수 있습니다. 그러나 그것은 무엇을 의미하며, 왜 우리는 그것을 필요로 합니까?

Pinning하는 이유

피닝을 사용하면 객체가 절대 움직이지 않을 수 있습니다. 이것이 왜 필요한지 이해하려면 async/.await 작동하는 방법을 기억해야합니다. 다음 코드를 고려하십시오.

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

내부적으로 그것은 Future를 구현하는 익명 type을 만들며 다음과 같은 poll 메소드를 제공합니다.

// The `Future` type generated by our `async { ... }` block
struct AsyncFuture {
    fut_one: FutOne,
    fut_two: FutTwo,
    state: State,
}

// List of states our `async` block can be in
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을 폴링합니다. fut_one이 완료할 수 없다면 AsyncFuture::poll이 반환됩니다. poll에 대한 Future의 호출은 이전 항목이 중단 된 위치로 선택됩니다. 이 과정은 future가 성공적으로 완료 할 때까지 계속됩니다.

그러나 reference를 사용하는 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], // points to `x` below
}

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

여기서 ReadIntoBuf future는 우리 구조체의 다른 분야, x에 대한 참조를 보유 합니다. 그러나 AsyncFuture가 이동하면 x의 위치 또한 이동하여 read_into_buf_fut.buf에 저장된 포인터를 무효화 합니다.

메모리의 특정 지점에 future을 고정하면 이 문제를 방지 할 수 있습니다. 위치를 고정하는 방식으로 async 블록 내부의 값에 대한 참조를 생성하는 것이 안전합니다.

Pinning 사용법

Pin type은 포인터 유형을 감싸서 내부에 있는 값을 이동하지 않는다고 보장합니다. 예를 들어 Pin<&mut T>, Pin<&T>, Pin<Box<T>>T가 움직이지 않을 것을 보장합니다.

대부분의 type은 이동하는 데 문제가 없습니다. 이러한 type은 Unpin이라고 하는 trait을 자동으로 구현합니다. Unpin 타입에 대한 포인터는 자유롭게 삽입하거나 Pin으로 부터 가져갈 수 있습니다. 예를 들어, u8Unpin이므로 Pin<&mut u8>은 다음과 같이 정상적인 &mut u8처럼 동작합니다.

어떤 함수는 futures를 Unpin해야 합니다. Unpin이 아닌 Future 또는 StreamUnpin type을 필요로 하는 함수와 함께 사용하려면 Box::pin(Pin<Box<T>>생성) 또는 pin_utils::pin_mut! 매크로 (Pin<&mutT>를 만들기 위해)를 사용하여 값을 고정해야 합니다. Pin<Box<Fut>>Pin<&mut Fut>은 모두 futures로 사용되며 둘 다 Unpin을 구현합니다.

예를 들면 다음과 같습니다.

use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io

// A function which takes a `Future` that implements `Unpin`.
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { /* ... */ }

let fut = async { /* ... */ };
execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait

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

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