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 #}