目次
19.4.0 関数とクロージャについての発展的な事項についての概要
-
関数ポインタ
- 関数を引数にとる関数を定義することができる
- このとき、引数の型には
fn
を指定する -
fn
は関数ポインタと呼ばれる -
fn
はすべてのクロージャトレイトFn
,FnMut
,FnOnce
を実装している- → クロージャを引数にとる関数・メソッドには常に関数を渡すことができる
- Enum と関数クロージャを組み合わせる書き方もあるので注意すること
- 関数ポインタ
fn
は関数の戻り値の型に使うことはできないので注意
-
クロージャを返却する関数
- クロージャを返却する関数を記述する際は、クロージャトレイトのトレイトオブジェクトを返却する関数として実装すること
19.4.1 関数ポインタ
-
クロージャだけでなく、通常の関数も関数に渡すことができる
- これは、定義済みの関数を関数に渡したいときに便利(既存の関数をクロージャとして定義しなおす必要がない)
-
関数は関数ポインタ
fn
型に型強制される- クロージャを表す型
Fn
と混同しないように注意 -
fn
は型、Fn
はトレイト
- クロージャを表す型
-
例:
// 普通の関数 fn add_one(x: i32) -> i32 { x + 1 } // 関数を引数にとる関数 // 引数にとった関数を二回実行し、それらの結果を足して返す fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { f(arg) + f(arg) } fn main() { // 関数を引数にとる関数を実行 let answer = do_twice(add_one, 5); println!("The answer is: {}", answer); // The answer is: 12 }
-
関数型
fn
はすべてのクロージャトレイト(Fn
,FnMut
,FunOnce
)を実装している- つまり、クロージャを受け取る関数の引数に対しては常に関数を渡すことができる
- なので、"関数的なもの"を受け取る関数を実装する際は、適当なトレイト境界を用いてクロージャを受け取る関数を書いて、クロージャと関数のどちらも受け取れるようにするのがベスト
-
ただし、クロージャではなく
fn
だけを受けとるような関数を実装したくなる場合もある- 最たる例は、クロージャを持たない外部コードとのインターフェイス
- たとえば、C言語の関数は関数を引数として受け取ることができるが、C言語にはクロージャがない
- 最たる例は、クロージャを持たない外部コードとのインターフェイス
-
インラインのクロージャも名前付き関数も使うことのできる場合の例:以下のコードは、さらにその下のコードと等価である
// 数値のベクタを文字列のベクタに変換する例 let list_of_numbers = vec![1, 2, 3]; let list_of_strings: Vec<String> = list_of_numbers.iter().map(|i| i.to_string()).collect();
// to_string 関数は複数あるので、引数に渡す関数はフルパス記法で指定する必要があることに注意 let list_of_numbers = vec![1, 2, 3]; let list_of_strings: Vec<String> = list_of_numbers.iter().map(ToString::to_string).collect();
関数ポインタを Enum と組み合わせる
-
例えば、以下のような Enum を考える
enum Status { Value(u32), stop, }
-
このとき、
Status::Value
の値は以下のように初期化できる
let status = Status::Value(1);
-
これは、
Status::Value
がStatus
型の初期化関数になることを表す -
この初期化関数も関数ポインタとして使用できる
-
例:
let list_of_statuses: Vec<Status> = (0u32..20).map(Status::Value).collect();
19.4.2 クロージャを返却する関数
-
クロージャはトレイトによって表現されるので、クロージャを直接関数の返り値の型には指定できない
-
関数からトレイトを返却したいとき、多くの場合、代わりにそのトレイトを実装する型を関数の返り値の型に指定することで対応できる
-
しかし、この方策はクロージャに対しては有効ではない
- なぜならば、クロージャを実装する返却可能な具体的な型がないから
- なお、クロージャトレイトを実装する
fn
は、返り値の型として使うことが許容されていない
- なお、クロージャトレイトを実装する
- なぜならば、クロージャを実装する返却可能な具体的な型がないから
-
クロージャを返却する関数を実装したい場合は、トレイトオブジェクトを使用すること
-
例:
fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) }
-
上記のコードで
Box
を使わずにそのままFn(i32) -> i32
を返そうとすると、Sized
トレイト関連のコンパイルエラーが発生する- これは、トレイトが動的サイズ決定型だからである
- 19.3 節で述べたように動的サイズ決定型を使用する際は、ポインタと組み合わせる必要がある