カスタム Future
カスタム Future を実装する (Pin, Waker, Poll)
低レベルな制御が必要な場合、たとえばカスタムのハードウェアドライバー、特殊なタイマー、
あるいはゼロアロケーションのプロトコルパーサーでは、async/await に頼る代わりに
Future を手で実装できます。
すべてのカスタム future は、3 つの概念を扱う必要があります:
| 概念 | 重要である理由 |
|---|---|
Pin<&mut Self> | 最初の poll の後に future がメモリ上で移動しないことを保証します。これは自己参照する future(たとえば .await ポイントをまたいで borrow を保持するもの)にとって重要です。構造体に通常のフィールドしか含まれていない場合(自己参照がない場合)、コンパイラは Unpin を自動実装するため、pinning は実質的に no-op です。 |
Poll::Pending / Poll::Ready | Pending は 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));
}