update.概要Rust勉強中です。ついて、主に以下の観点で調べたので、備忘録的にまとめます。クロージャを定義したとき、どのtraitが実装されるのかクロージャを扱うときにどのtraitを使えばいいのか誤りなどありましたらご指摘いただければと思います。参考文献は末尾にまとめました。(2018/1/12時点の最新のstable)に基づきます。FnについてRustでクロージャを定義すると、コンパイル時に内部的に匿名の構造体が作られます。この型はコンパイル時にしか分からないので、たとえば「クロージャを受け取る関数」を作ろうと思っても、クロージャの型を直接指定することはできません。いずれか(もしくは全て)のtraitを実装します。Fnの三つのtraitがあり、それぞれどう使い分けられるのかがこの記事の主題になります。それを見ていく前に、それぞれのtraitの定義を確認しておきたいと思います。selfと&selfのいずれを受け取るかです。一つ重要なのは、FnがFnMutを、FnMutがFnOnceを継承していることです。次の項で詳しく見ますが、これらはクロージャ間の包含関係を表しています。介した呼び出しはselfの所有権をとるので、FnOnceは(その名の通り)一回しか呼び出せないことにも注意が必要です。どのtraitがいつ実装されるか本題です。上述の通り、クロージャが実装しうるtraitは三つあるので、「じゃあ、どう使い分けられているのか?」という疑問が湧きます。結論から言えば、各traitは、それぞれクロージャが特定の条件を満たしているかどうかに応じて、自動的に実装されるかどうかが変わります:FnOnceは、全てのクロージャが実装しているFnMutは、キャプチャした変数をmoveしない全てのクロージャが実装しているFnは、キャプチャした変数をmoveせず、書き換えもしない全てのクロージャが実装している逆から言えば、以下のようになります。上記は暗記する必要はなく、以下のことを考慮すれば自然に導けます。...};このとき、内部的に以下のような型が作られます。Dataといった実体をメンバに持つと考えられます。後述のmoveキーワードを付けたときも、実体を持つと考えられます)そして、以下のそれぞれのimplが可能なら作られます。}}各「...」のところには、クロージャの定義時に書いた処理の中身が入ります(この際、xはself.xに置き換えます)。ここで、「可能なら」と言ったのは、処理の中身によっては定義できないことがあるからです。処理の中でxを書き換えているならば、&selfを受け取るFnの定義は成立しません。同様に、処理の中でxの所有権を手放しているならば、selfの所有権をとらないFnMutやFnの定義は成立しません。このことから、処理の中身に応じて自然に「三つのtraitのどれが実装されるか」が決まると考えられます。以下、具体的な例を見ていきます。FnOnceのみ実装キャプチャした変数の所有権をどこかにmoveしている場合、FnOnceのみが実装されます。consume(self.x);}Fnも同様です。上述のように、FnOnceしか実装していないクロージャは、一回しか呼び出せません。キャプチャした変数の所有権を初回呼出時に手放してしまうことを考えれば、納得できます。FnOnceとFnMutのみ実装キャプチャした変数を書き換えているが所有権のmoveはしていない場合、FnOnceとFnMutが実装されます。上記のいずれにも当てはまらない場合、FnOnceとFnMutとFnが全て実装されます。キャプチャした変数がCopyトレイトを実装している場合は、所有権の移動は起こりません。そのため、Copyトレイトを実装している型の変数(i32など)をキャプチャしても、FnOnceだけが実装されることはなく、常にFnMutもしくはFnまで実装されます。not_consume(x);});こちらについても、同様の考え方で納得できるかと思います。moveクロージャについてクロージャの定義時にmoveキーワードを使うことで、キャプチャした変数の所有権のクロージャへの移動を強制します。Data,}自分は最初にmoveの説明を聞いた時、所有権と関係するということで、「FnOnceと何か関係があるのか?」と少し混乱しました。実際はmoveを付けてもFnOnce、FnMut、Fnのどれが実装されるかには影響しません。上の例1~例4は全て、クロージャにmoveを付けてもそのまま成り立ちます。これは、最初に述べた考え方に沿って考えれば納得できるかと思います。実際、moveとFnOnceは、以下のようにそれぞれ別の段階での所有権の移動に紐づいています。moveは環境からクロージャへの所有権の移動を強制するFnOnceのみ実装されるのはクロージャから外部の関数等への所有権の移動があるときクロージャを受け取るときにどのtraitを使うべきか実際に「クロージャを受け取る関数」を書くときに、どのtraitで受け取るべきかを考えてみます。上で説明したように、クロージャの間には以下の包含関係があります。これは、もしクロージャを受け取るときにFnで受け取るようにすると、一部のクロージャが受け取れないことを意味します。使えるクロージャに不必要な制限は設けたくないので、可能な限りFnよりFnMut、またFnMutよりFnOnceでクロージャを受け取るべき、と考えられます。いうことで、基本的には以下のように決めればよいと思います。渡されたクロージャを複数回呼び出す可能性があるなら、FnMutで受け取る標準ライブラリでは、Iteratorの各種メソッドなどが該当クロージャのimmutable性を要求したい場合はFnで受け取るこちらは少し探してみても標準ライブラリでは例が見つかりませんでしたたとえば、複数のスレッドから一つのクロージャを呼び出したいときなどには該当するかと思われますクロージャを返すときにどのtraitを使うべきか同様に、クロージャを返す時にどのtraitを使うべきか考えます。前提として、前提で述べたようにクロージャの具体的な型はコーディング時には分からないので、クロージャを返す時には、Boxでラップしてトレイトオブジェクトとして返却する必要があります。9今度は引数で受け取るときとは逆に、「FnはFnMutでもあり、FnMutはFnOnceでもある」ことを考えると、可能な限りFnOnceよりもFnMut、またFnMutよりもFnとして返したほうが、呼び出し側での融通が効いて良いと思われます。(なお、FnOnceはそのままBoxに入れても呼び出せないので注意。以下のページを参考にしました。upLoginユーザーは見つかりませんでした。