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

再帰

内部的には、async fn.await される各サブFutureを含むステートマシン型を作成します。これにより、再帰的な async fn は少し厄介になります。結果として得られるステートマシン型が自分自身を含まなければならないからです。

#![allow(unused)]
fn main() {
async fn step_one() { /* ... */ }
async fn step_two() { /* ... */ }
struct StepOne;
struct StepTwo;
// この関数は:
async fn foo() {
    step_one().await;
    step_two().await;
}
// 次のような型を生成します:
enum Foo {
    First(StepOne),
    Second(StepTwo),
}

// したがって、この関数は:
async fn recursive() {
    recursive().await;
    recursive().await;
}

// 次のような型を生成します:
enum Recursive {
    First(Recursive),
    Second(Recursive),
}
}

これは動作しません。無限サイズの型を作成してしまったからです! コンパイラは次のようにエラーを出します。

error[E0733]: recursion in an async fn requires boxing
 --> src/lib.rs:1:1
  |
1 | async fn recursive() {
  | ^^^^^^^^^^^^^^^^^^^^
  |
  = note: a recursive `async fn` call must introduce indirection such as `Box::pin` to avoid an infinitely sized future

これを可能にするには、Box を使って間接参照を導入する必要があります。

Rust 1.77 より前では、コンパイラの制限により、recursive() への呼び出しを Box::pin でラップするだけでは十分ではありません。これを動作させるには、recursive.boxed() された async ブロックを返す非 async 関数にする必要があります。

#![allow(unused)]
fn main() {
use futures::future::{BoxFuture, FutureExt};

fn recursive() -> BoxFuture<'static, ()> {
    async move {
        recursive().await;
        recursive().await;
    }.boxed()
}
}

新しいバージョンの Rust では、そのコンパイラの制限は解除されました

Rust 1.77 以降、割り当てによる間接参照を伴う async fn における再帰のサポートが安定化されたため、関数の状態が無限サイズになるのを避けるために何らかの形式の間接参照を使っている限り、再帰呼び出しが許可されます。

これは、次のようなコードが動作するようになったことを意味します。

#![allow(unused)]
fn main() {
async fn recursive_pinned() {
    Box::pin(recursive_pinned()).await;
    Box::pin(recursive_pinned()).await;
}
}