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으로 부터 가져갈 수 있습니다.
예를 들어, u8
은 Unpin
이므로 Pin<&mut u8>
은 다음과 같이 정상적인 &mut u8
처럼 동작합니다.
어떤 함수는 futures를 Unpin
해야 합니다.
Unpin
이 아닌 Future
또는 Stream
을 Unpin
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