Rustコトハジメ

Rustで競技プログラミングをやる人の覚書のようなものです。

なぜFuture::pollはPinをとるのか?

Futureトレイトはこのような形をしている。

trait Future {
    type Output;
    fn poll(
        // Note the change from `&mut self` to `Pin<&mut Self>`:
        self: Pin<&mut Self>,
        // and the change from `wake: fn()` to `cx: &mut Context<'_>`:
        cx: &mut Context<'_>,
    ) -> Poll<Self::Output>;
}

executerはpollを呼び、値がReadyとなることを期待する。もしNotReadyである場合は、下からwakeされるまで待つ。Futureというのは、ステートマシンだといえる。

本質的には、

trait SimpleFuture {
    type Output;
    fn poll(&mut self, wake: fn()) -> Poll<Self::Output>;
}

のようなものと変わりないが、wakeにコンテキスト(データ)が必要な場合に対応するために、Contextという下に隠す設計になっている。

なぜこのPinが必要かというと、async fn構文のためである。async fn構文を使うことによって、awaitするところを状態としたステートマシンを自動生成することが出来る。async fnは中で巨大なFutureを生成する。

さて、もしここで、async fnの中で参照を使っていて、そのFutureをmoveしようとするとどうなるかというと、この時にポインタの値が変わってしまう。moveが出来ないというのはRustプログラミングの中では致命的なので、この制約を取り去りたい。例えば実際に、mapする場合などはmoveする。

シンプルな例でいうとこのような場合である。

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?>,
}

それでは困るため、Pinを使う。Pinは、&mut T, &T, Box<T>などに対してTのアドレスを固定(ピン)する。やり方の一つとしては、Box::pinがある。

pub fn pin(x: T) -> Pin<Box<T>>

Pinの逆Unpinは、中のTを取り出したり、書き換えたりする能力を持つ。moveしても問題が起こらない型については、Unpinが実装されている。