この資料について
この資料は「Rust Book 勉強会 #9」用の発表資料です。
- 担当: Yuya Kato (Nayutaya Inc.)
- 範囲: 第19章「Advanced Features」 第5節「Advanced Functions and Closures」
- 原文: Advanced Functions and Closures - The Rust Programming Language
- 日本語版: 高度な関数とクロージャ - The Rust Programming Language
高度な関数とクロージャ
本節には、以下のトピックが含まれます。
- 関数ポインタ
- 関数からのクロージャの返却
関数ポインタ
関数ポインタ
クロージャを関数に渡す方法は既に述べた通りですが、クロージャと同様、普通の関数も関数に渡すことができます。それにより、新しいクロージャを作ることなく、定義済みの関数を関数に渡すことができます。
関数は、fn
型(小文字のf)に型強制(coerce)されます。fn
型は「関数ポインタ」と呼ばれます。
大文字のFから始まる「Fn
クロージャトレイト」と混同しないようにしましょう。
関数ポインタを受け取り、呼び出す
関数ポインタを受け取る引数の書き方は、クロージャのそれに似ています。以下にコード例を示します。
// Listing 19-35: Using the fn type to accept a function pointer as an argument
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);
}
do_twice
関数の引数に指定されているfn(i32) -> i32
型は「i32
を受け取り、i32
を返す関数のポインタ」を示します。
関数ポインタとクロージャ
関数ポインタは、クロージャトレイトの3つすべて(Fn
、FnMut
、FnOnce
)を実装します。そのため、クロージャを期待する関数に渡すことができます。
関数が引数として、関数ポインタとクロージャのどちらも受け入れられるように、ジェネリックな型とクロージャトレイトの1つを使用して関数を書くのが最善です。
関数ポインタだけを受け入れる関数
クロージャではなく、fn
型だけを受け入れたくなる場合もあります。例えばC言語とインターフェースする場合です。C言語の関数は関数ポインタを受け入れられますが、C言語にはクロージャはありません。
map
関数の例
map
関数を使い、数値のベクタを文字列のベクタに変換する例を示します。クロージャを用いる例は、以下の通りです。
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(|i| i.to_string()) // クロージャ
.collect();
続いて、関数ポインタを用いる例は、以下の通りです。
let list_of_numbers = vec![1, 2, 3];
let list_of_strings: Vec<String> = list_of_numbers
.iter()
.map(ToString::to_string) // 関数ポインタ
.collect();
関数ポインタを用いる場合、フルパス記法を使う必要があることに気を付けましょう。to_string
関数は複数あり、どの関数なのかを特定する必要があります。
どちらを用いるかは好み次第です。この2つのコードは、結果的に同じコードにコンパイルされます。
関数からのクロージャの返却
関数からのクロージャの返却 (1/2)
クロージャはトレイトによって表現されます。そのため、クロージャを関数の戻り値として直接返却することはできません。通常、トレイトを実装した具体的な型を関数の戻り値として指定できますが、クロージャは返却可能な具体的な型を持たないため、それができません。
例えば、クロージャトレイトFn
を戻り値の型として使うことはできません。例を以下に示します。
fn returns_closure() -> Fn(i32) -> i32 {
|x| x + 1
}
コンパイルエラーは以下の通りです。
error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static:
std::marker::Sized` is not satisfied
-->
|
1 | fn returns_closure() -> Fn(i32) -> i32 {
| ^^^^^^^^^^^^^^ `std::ops::Fn(i32) -> i32 + 'static`
does not have a constant size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for
`std::ops::Fn(i32) -> i32 + 'static`
= note: the return type of a function must have a statically known size
関数からのクロージャの返却 (2/2)
前述の例は、トレイトオブジェクトを使うことで実現できます。例を以下に示します。
fn returns_closure() -> Box<dyn Fn(i32) -> i32> {
Box::new(|x| x + 1)
}
トレイトオブジェクトの詳細については第17章の「Using Trait Objects That Allow for Values of Different Types」を参照してください。