本セッションの登壇者
セッション動画(YouTubeチャンネル登録もお願いします。)
こんにちは。 TechFeed公認Expert Rustのkeenです。
今日は「DropとFutureとDropのfuture(未来)」について話していこうと思います。 一見ややこしそうなタイトルですが、上級テクニックということでややこしい話をしていきます。
dropとは
皆さんご存知だと思いますが、Rustにはdropという概念があります。dropでは、panic以外のいろいろな処理が走ります。
たとえば、BufWriterのflushだとメモリ上に残ってる値があるとします。すると、その値をディスクに吐き出す処理などが走ったりします。
dropが呼ばれるタイミング
次に、dropが呼ばれるタイミングについてお話します。
dropが呼ばれるタイミングというのは意外とたくさんあります。 皆さん、以下のコードでどの箇所でdropが呼ばれるかわかりますか?
けっこうたくさんあります。 たとえば、値が再代入されるときや再代入が中で走るような関数を呼び出したとき、また panicのときも走ります。
すこしトリッキーなのが、_ に代入するときは※データがムーブすると言われているので、ここでもdropが走ります(※スピーカーより: _ではなく_varの間違いでした→参照)。
そして、スコープの終わりにはこのvalueのdropが走ったりします。
async
次に、asyncについてお話します。
async fnと書くと脱糖されて、impl Futureに書き換えられます。 awaitはasyncの中でしか使えないようになっています。そして、asyncの中で同期IOをするのはよくないとされています。
ここまでがおさらいでした。
FutureとDrop - わりと難しいasync dropの問題
これからfutureとdropの話をしていきます。
仮にTlsStreamがdropを実装していたとしましょう。
この場合は、スコープの最後でdropが走ります。
このdropですが、TlsStreamの終了処理ではおそらくIOが走るので、このdropを async にしたくないですか ? というのが、今日の主題です。つまり、これはasync dropと呼ばれている機能で、この紹介になります。
async drop、やればいいじゃんと思いますが、これがけっこう難しいです。具体的には、主に以下の3点が理由です。
- dropとasync dropどっちを呼ぶの ?
- syncの中でasync drop呼んでいいの ?
- async dropのキャンセルとか
ほかには、traitオブジェクトやasyncの状態管理などいろいろな問題がありますが、今日はこの3点についてお話していきます。
drop or async drop?
まず、ひとつめの「dropとasync dropどっちを呼ぶの ?」について説明していきます。
わりとわかりやすい例ですが、ひとつの構造体がdropとasync dropを実装していた場合に、スコープの終わりにどっちを呼べばいいんでしょう ?
たとえば、同期だったらdropを呼んで、非同期だったらasyncを呼べばいいという気もします。
また、dropを実装していなくてasync dropしか実装してしていなかった場合には、dropを呼べないけどどうするの ? などのややこしいことが起こるわけです。
async drop in sync
次は、「async dropの中でsyncを呼んだらどうなるの ?」 という話をします。
たとえば、fn just_dropでasync dropを実装していたとすると、おそらくasync_drop().awaitのようなものがコンパイラ内部で呼ばれることになるでしょう。
このawaitはasyncの中でしか使っちゃいけなかったはずなので、これダメだよねということになります。
たとえば、「awaitに渡すのが悪いじゃん」ってことで、async dropを実装した型を同期関数に渡すのはダメというルールを作るとします。option::insertとかは内部で dropするので、こういった関数に渡せなくなります。
つまり、思った以上にいろいろな関数で使えなくなり、かなり不便になってしまいます。
すこしややこしいので、まずfutureの実行についておさらいしましょう。
以下のように非同期なコードがあったとします。
またawaitがいくつかあったとします。
この場合、コンパイラがこのawaitを認識して、そのawaitの前後でコードを分割します。
それぞれでfutureの状態を作り、1個を実行する度にpollを呼びます。
仮にpendingだったら、元に戻ってもう1回pollを呼びます。また、readyだったら次のpollに進み、さらにreadyだったらさらに次に進むというように実行が進行します。
async dropのキャンセル
このfutureですが、futureはデータ型なので、dropできます。
どこでdropできるかというと、非同期ランタイムがpollを呼ぶタイミングでpollを呼ばずに、そのまま値を捨てるとdropできます。
このfutureをdropすると何が起きるかというと、これはfutureのキャンセルと決められています。
話をまとめると、このpollはもともとこのawaitがあった箇所に入るので、awaitを書いたところで、コードはキャンセル可能となります。
それを踏まえた上で、futureのasync dropを見ていくと、TlsStreamがasync dropを実装したとして、ここで上書きしたとします。
すると、ここでasync dropが走ります。ここにawaitが現れるわけです。
つまり、何の変哲もない普通のコードが急にキャンセル可能になってしまいます。
これはよくないと言われています。
解決策として、たとえばawaitがキャンセルを明示する方法だと思うことにします。すると、ここの=に.awaitを書かせるのか ? などいろいろな議論がされています。また、そもそもfutureがキャンセル可能なのがよくないんじゃないか ? とも言われています。なので、futureのキャンセル可能という仕様をやめようとも言われています。
まとめ
テーマの「DropとFutureとDropのfuture(未来)」について話をまとめます。
async drop は望まれている機能ですが、合理的な設計が難しく、どう設計するのか という話でした。
最後に参考資料を紹介して終わりにします。
- Async destructors, async genericity and completion futures - Sabrina Jewson
- asyncdrop/async-dest.md at main vzezda/asyncdrop
- Destructors - The Rst Reference
ありがとうございました。