Rustコトハジメ

プログラミング言語Rustに関する情報をお届けします。

クロージャのスーパートレイト実装はコンパイラが生成してる

https://www.rustforbeginners.com/entry/ja/2019/01/05www.rustforbeginners.com

で、クロージャコンパイラが生成しているということをお話しました。

しかし、積み残し課題として、自身のスーパートレイトがどうやって実装されているのかわからないという話をしました。

スーパートレイトというのは、3つのクロージャの間にはFn: FnMutFnMut: FnOnceという親子関係があるので、環境に対して副作用を与えないFn型のクロージャを定義したとしても、FnMutFnOnceが実装されていなければならないのです。

https://www.rustforbeginners.com/entry/ja/2019/01/10www.rustforbeginners.com

では、Iteratormapなどの関数がFnMutをとるということをお話しましたが、これに対してFnを与えることが出来るのも、Fn: FnMutであるおかげなのです。

今回、このスーパートレイトがどのように実装されているか話している記事を見つけたので紹介します。

ライブラリのブランケット実装は参照に対して定義してるので違う

Rustでは多くの場合、ライブラリのブランケット実装によって解決しています。しかし、ライブラリを探しましたが、

impl<A, F> FnMut<A> for F where F : Fn<A>
impl<A, F> FnOnce<A> for F where F: FnMut<A>

に相当する実装は見つかりませんでした。

function.rs.html -- source

にあるブランケット実装は、すべて&F&mut Fに対して実装されています。具体的には、以下の5つです。

  • impl Fn for &F where F: Fn
  • impl FnMut for &F where F: Fn
  • impl FnOnce for &F where F: Fn
  • impl FnMut for &mut F where F: FnMut
  • impl FnOnce for &mut F where F: FnMut

上のmapの例でいうと、Fnが突然&Fnに変換されて、impl FnMut for &F where F: Fnが適用されるのかな?と思ってましたが、もしそうであれば、

https://www.rustforbeginners.com/entry/ja/2019/01/07www.rustforbeginners.com

のようなマクロ実装は必要ないため、違うと思いました。

コンパイラが生成してると言ってる記事を発見した

Finding Closure in Rust | Huon on the internet

は、公式のブログ(Abstraction without overhead: traits in Rust | Rust Blog)からも参照されている記事なので信頼出来ると思います。この記事では、

  • and similarly every closure implementing FnMut can also implement FnOnce
  • the former is more flexible, so the compiler implements FnMut for closure (and also FnOnce)

つまり、コンパイラがスーパートレイトについても実装しているということを言っています。

考えてみれば、スーパートレイトについては「実装しないといけない」ものなので、ライブラリのブランケット実装による静的ディスパッチで生成する意味はなく、コンパイラが実装するという動作は理にかなってると言えます。