Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

カスタム Future

カスタム Future を実装する (Pin, Waker, Poll)

std-badge cat-concurrency-badge cat-rust-patterns-badge

低レベルな制御が必要な場合、たとえばカスタムのハードウェアドライバー、特殊なタイマー、 あるいはゼロアロケーションのプロトコルパーサーでは、async/await に頼る代わりに Future を手で実装できます。

すべてのカスタム future は、3 つの概念を扱う必要があります:

概念重要である理由
Pin<&mut Self>最初の poll の後に future がメモリ上で移動しないことを保証します。これは自己参照する future(たとえば .await ポイントをまたいで borrow を保持するもの)にとって重要です。構造体に通常のフィールドしか含まれていない場合(自己参照がない場合)、コンパイラは Unpin を自動実装するため、pinning は実質的に no-op です。
Poll::Pending / Poll::ReadyPending は executor に「まだ完了していない」ことを伝え、Ready(value) は future を完了させます。
cx.waker()executor が渡してくるハンドルです。Pending を返した後は、どこかの時点で必ず wake() を呼び出さなければなりません。そうしないと executor はその future を二度と poll せず、処理は停止したままになります。

以下の例では、期限を過ぎた後に完了する単純な Delay future を構築します。
これは自己参照フィールドを持たないため、自動的に Unpin になります— pinning にコストはかかりません。実運用のタイマーであれば reactor に登録するでしょう。 ここでは executor が再度 poll するように、ただちに wake_by_ref() を呼び出します。

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::{Duration, Instant};

/// A hand-rolled future that resolves after a deadline.
///
/// This is intentionally simple—no timer wheel, no reactor—just a
/// busy-polling future that shows the three things every custom
/// `Future` must handle:
///
/// 1. **`Pin<&mut Self>`** — guarantees our struct won't move in
///    memory.  Because `Delay` contains only `Instant` and
///    `Duration` (both `Unpin`), the compiler auto-implements
///    `Unpin` for us and pinning is a no-op here.  If the struct
///    held a self-referential borrow (like a hand-written async
///    state machine), pinning would *prevent* the struct from
///    being moved after the first poll, which would invalidate
///    the borrow.
///
/// 2. **`Poll::Pending` vs `Poll::Ready`** — returning `Pending`
///    tells the executor "I'm not done yet; wake me later."
///
/// 3. **`cx.waker()`** — the mechanism to *schedule* a re-poll.
///    Without calling `wake()`, the executor would never poll us
///    again and the future would hang.
struct Delay {
    /// When we should resolve.
    deadline: Instant,
}

impl Delay {
    fn new(dur: Duration) -> Self {
        Self {
            deadline: Instant::now() + dur,
        }
    }
}

impl Future for Delay {
    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        if Instant::now() >= self.deadline {
            // Deadline reached — resolve the future.
            Poll::Ready(())
        } else {
            // Not ready yet.
            //
            // We MUST arrange for `wake()` to be called later or the
            // executor will never poll us again.  A production timer
            // would register with a reactor; here we just ask to be
            // re-polled immediately. The executor may yield to other
            // tasks before coming back to us.
            cx.waker().wake_by_ref();
            Poll::Pending
        }
    }
}

#[tokio::main]
async fn main() {
    let start = Instant::now();
    println!("waiting 10 ms …");

    // Use the custom future just like any other async expression.
    Delay::new(Duration::from_millis(10)).await;

    println!("done in {:?}", start.elapsed());
    assert!(start.elapsed() >= Duration::from_millis(10));
}