Rustにおける関数ポインタやクロージャ
Rustにおける関数ポインタやクロージャの扱いを整理する
Rustにおける関数ポインタ
fn
というキーワードを用いると関数ポインタ型を指すことができる。
これを利用することで以下のようなコードが書ける
fn func(x: i32) -> i32 {
x * x
}
fn apply_twice(x: i32, f: fn(i32) -> i32) -> i32 {
f(f(x))
}
fn main() {
println!("{}", apply_twice(3, func));
}
ここで注意するのはfunc
の型は正確には関数ポインタ型ではなく、関数定義型(Function item types)という型になっている。
上の例でfunc
がapply_twice
の引数に取れるのは関数定義型から関数ポインタ型への型強制が行われているからである。
関数定義型は型としての情報に関数の引数や返り値の型のみならず、関数名などの情報も含まれていて、その関数ただ1つを指すようになっている。
関数ポインタ型はunsafe
キーワードによる安全性の情報やextern
のようなABIの情報も含まれる。
Fnなどのトレートとの違い
fn
による関数ポインタ型はそれ自体で型であるが、Fn
・FnOnce
・FnMut
はトレートなので、それ自体は型でない。よって完全に別物である。
つまり
fn apply_twice(x: i32, f: Fn(i32) -> i32) -> i32 {
f(f(x))
}
というようには書けない。このようなことをしたい場合は、ジェネリクスを使いFn
を実装した型を要求するという形にするか、dyn
キーワードを使う。
ジェネリクスの場合は、
fn apply_twice<F>(x: i32, f: F) -> i32 where F: Fn(i32) -> i32 {
f(f(x))
}
dynキーワードの場合は
fn apply_twice(x: i32, f: &(dyn Fn(i32) -> i32)) -> i32 {
f(f(x))
}
関数定義型はFn
・FnOnce
・FnMut
を実装しているので、apply_twice
を上のように置き換えても先程のコードは動く(dynを使う場合はfunc
を参照として渡す)。
関数ポインタ型もsafe
な場合に限り、これらのトレートを実装している。
クロージャ型
クロージャ表現によりつくられるクロージャはクロージャ型というこれまた別の型を持っていて、関数ポインタ型や関数定義型とは違う。
fn debug_call<F>(f: F) where F: FnOnce() -> String {
println!("{}", f());
}
fn main() {
let mut a = String::from("");
let b = String::from("moved");
let f1 = || String::from("test");
let f2 = move || b;
let f3 = || { a.push('a'); String::from("modified") };
debug_call(f1);
debug_call(f2);
debug_call(f3);
println!("{}", a)
}
先程の例とは違い、Fn
ではなくFnOnce
を要求している点に注意。Fn
は複数回よべる関数である必要があるが、f2
はmove
を使っていくつかの値の所有権を奪っている。
そのため、複数回呼び出しすると所有権を複数回奪うことになるのでそもそも複数回呼べない。
また、Fn
はキャプチャーした環境に対して変更を行うことができない。f3
は内部でa
に対してpush
を呼び出しているが、このためにはa
へのミュータブルな参照を取る必要がある。よってこちらもFn
は実装できず、FnMut
が実装されている。
FnOnce
はFnMut
とFn
が実装された型であれば必ず実装されているため、すべてのクロージャを取ることができる(FnMut
とFn
はFnOnce
のサブトレートである)。
また、Fn
が実装されていればFnMut
が実装されている(Fn
はFnMut
のサブとレートである)。
おまけ
関数定義型は関数名を型情報の中に含む。そのため、std::any::type_name
をつかって型を出力すると関数名を出力できたりする。
実行例は以下の通り
Discussion