Pinning
想要輪詢 future 前,必須先用一個特殊的型別 Pin<T> 固定住 future。如果你閱讀了前一節 揭秘:執行 Future 與任務對 Future trait 的解釋,你會在 Future:poll 方法的定義上 self: Pin<&mut Self> 認出 Pin,但它到底代表什麼,為什麼需要它?
為什麼要 Pinning
Pinning 達成了確保一個物件永遠不移動。我們需要憶起 async/.await 的運作原理,才能理解為什麼需要釘住一個物件。試想以下的程式碼:
# #![allow(unused_variables)] #fn main() { let fut_one = ...; let fut_two = ...; async move { fut_one.await; fut_two.await; } #}
神秘面紗之下,其實建立了一個實作了 Future 的匿名型別,它的 poll 方法如下:
# #![allow(unused_variables)] #fn main() { // 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 { fn poll(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 會直接返回。接下來的 Future 的 poll 呼叫則從前一次結束的地方繼續執行。這個過程會持續到整個 future 有辦法成功完成。
不過,如果 async 區塊(block)使用到引用(reference)會如何?舉例來說:
# #![allow(unused_variables)] #fn main() { async { let mut x = [0; 128]; let read_into_buf_fut = read_into_buf(&mut x); read_into_buf_fut.await; println!("{:?}", x); } #}
這會編譯成怎樣的結構體(struct)呢?
# #![allow(unused_variables)] #fn main() { 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 移動了(moved),x 的記憶體位址也會隨之移動,並讓儲存在 read_into_buf_fut.buf 的指標失效。
將 future 固定在一個特定的記憶體位址可以避免這種問題發生,讓在 async 區塊裡建立值的引用更安全。
如何使用 Pinning
Pin 型別封裝了指標型別,保證這些在指標背後的值不被移動。例如 Pin<& mut>、Pin<&t>、Pin<Box<T>> 全都保證 T 不被移動。
大多數的型別不會有移不移動的問題。這些型別實作了 Unpin trait。指向 Unpin 型別的指標可任意移入 Pin 或從 Pin 取出。舉例來說,u8 是 Unpin 的,因此 Pin<&mut T> 會表現得像普通的 &mut T。
有些與 future 互動的函式會要求 future 必須為 Unpin。若想要在要求 Unpin 型別的函式使用一個非 Unpin 的 Future 或 Stream,你要先透過 Box::pin(可建立 Pin<Box<T>>)或 pin_utils::pin_mut! (可建立 Pin<&mut T>)來固定住(pin)這個值。Pin<Box<Fut>> 與 Pin<&mut Fut> 就都能作為 future 使用,且都實作了 Unpin。
以下是範例:
# #![allow(unused_variables)] #fn main() { 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 #}