為了對Future呼叫poll,需要使用到Pin的特殊型別。本節就介紹一下Pin型別。
非同步背後的一些原理
例子1
- 原始碼
//檔案src/main.rs
use futures::executor;
async fn async_function1() {
println!("async function1 ++++ !");
}
async fn async_function2() {
println!("async function2 ++++ !");
}
async fn async_main() {
let f1 = async_function1();
let f2 = async_function2();
//重點關注這裡---------
let f = async move {
f1.await;
f2.await;
};
//---------------------
f.await;
}
fn main() {
executor::block_on(async_main());
}
- 配置,在Cargo.toml中新增
我們主要考慮async_main()函式中的async塊(async函式也是一樣,透過async都是轉化為Future),實際背後會做如下工作:[dependencies] futures = "0.3.4
(1)為async塊生成一個類似於如下的結構體:
struct AsyncFuture {
fut_one: FutFunction1,
fut_two: FutFunction2,
state: State,
}
//state的定義可能如下
enum State {
AwaitingFutFunction1,
AwaitingFutFunction2,
Done,
}
(2)為其生成對應的poll函式:
impl Future for AsyncFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
loop {
match self.state {
State::AwaitingFutFunction1 => match self.fut_one.poll(..) {
Poll::Ready(()) => self.state = State::AwaitingFutFunction2,
Poll::Pending => return Poll::Pending,
}
State::AwaitingFutFunction2 => match self.fut_two.poll(..) {
Poll::Ready(()) => self.state = State::Done,
Poll::Pending => return Poll::Pending,
}
State::Done => return Poll::Ready(()),
}
}
}
}
好,到這裡,我們只是模擬編譯器給async程式碼塊進行了展開,那麼和我們要講的Pin有什麼關係呢?
例子2
我們再考慮如下例子:
async fn async_put_data_to_buf(mut buf: &[u8]) {
//to do
...
}
async fn async_main () {
//重點關注這裡---------
let f = async {
let mut x = [0; 128];
let read_into_buf_fut = async_put_data_to_buf(&mut x);
read_into_buf_fut.await;
};
//---------------------
f.await;
}
對於上面async_main中的async塊,編譯器為其生成的結構如下:
struct ReadIntoBuf<'a> {
buf: &'a mut [u8], // points to `x` below
}
struct AsyncFuture {
x: [u8; 128],
read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}
在AsyncFuture中,read_into_buf_fut.buf指向x,相當於是一個自引用(一個欄位引用另一個欄位)。但是如果AsyncFuture發生移動,x肯定也會發生移動,如果read_into_buf_fut.buf還是指向原來的值的話,則會變成無效。
而Pin就是為了解決此問題的。
Pin介紹
Pin型別包著指標型別,保證指標背後的值將不被移動。例如 Pin<&mut T>,Pin<&T>, Pin<Box> 都保證 T 不會移動。
拿上面的例子來說,如果使用Pin<>就是將x對應的那塊記憶體固定,這樣即使AsyncFuture發生移動,但是x不會移動,那麼read_into_buf_fut.buf不會變成懸垂引用。
大多數型別都沒有移動的問題,這些型別實現了 Unpin trait。Unpin 型別指標可以自由從 Pin 中放入或取出。例如,u8 就是 Unpin的。
如果需要Future和Unpin一起使用,則需要使用Pin<Box>或者pin_utils::pin_mut!,例子如下:
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
本作品採用《CC 協議》,轉載必須註明作者和本文連結