Rustコトハジメ

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

Rustのクロージャはコンパイラによって生成されたクラスである

Rustにおける3つのクロージャ

Rustでは、クロージャはそれぞれ全く異なるクラスが生成され、それらがFnやFnMutといったトレイトを実装します。そして、Fn: FnMutFnMut: FnOnceという関係があります。ベン図で表すと以下のようになります。

f:id:akiradeveloper529:20190307141032j:plain

pub trait Fn<Args> : FnMut<Args> {
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;

pub trait FnMut<Args> : FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;

pub trait FnOnce<Args> {
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;

それぞれ、

  • Fn: 環境に対する副作用なし。共有出来る
  • FnMut: 環境に対する副作用あり。共有出来ない
  • FnOnce: 環境をドロップする。一回しか呼べない。共有出来ない

という性質があります。

共有借用と可変借用を混ぜた場合のクロージャを実験

環境に対してmutateする場合、クロージャはFnMutを実装するとドキュメントに書いてありますが、では共有借用(&)と可変借用(&mut)が混ざった場合にはどうなるのでしょうか?実験してみます。

fn main() {
    let mut v1 = vec![1,2];
    let v2 = vec![3];
    let mut f = || -> () {
        v1.push(v2[0]);
    };
    println!("{:?}", v2);
    println!("{:?}", v1); // error
}

これをコンパイルすると、

error[E0502]: cannot borrow `v1` as immutable because it is also borrowed as mutable

というエラーになります。v2については通るのですが、v1が通りません。

考察

これはつまり、自動生成されたクロージャv1については可変借用をして、v2については不変借用をしていることを意味しています。ここから、どのようなクラスが自動生成されているか考えると、

struct ClosureEnv<'a,'b> {
    v1_mut_borrow: &'a mut Vec<i32>,
    v2_borrow: &'b Vec<i32>
}

こういうことなのではないかと想像出来ます。これに対して、FnMutを実装しているということなのだと考えられます。

積み残し課題

FnMutのスーパートレイトのFnOnceがどこで実装されているのかは分かりませんでした。ふつうに考えると、標準ライブラリのどこかに

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

のような定義があれば良さそうですが、見つかりませんでした。&Fに対するブランケット実装ならばライブラリコードに見つかったのですが、それが探してるものなのか分かりません。分かったら別途記事にします。