Edited at

Rust Book 勉強会 #9 第19章「Advanced Features」 第5節「Advanced Functions and Closures」


この資料について

この資料は「Rust Book 勉強会 #9」用の発表資料です。



高度な関数とクロージャ

本節には、以下のトピックが含まれます。


  • 関数ポインタ

  • 関数からのクロージャの返却



関数ポインタ



関数ポインタ

クロージャを関数に渡す方法は既に述べた通りですが、クロージャと同様、普通の関数も関数に渡すことができます。それにより、新しいクロージャを作ることなく、定義済みの関数を関数に渡すことができます。

関数は、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つすべて(FnFnMutFnOnce)を実装します。そのため、クロージャを期待する関数に渡すことができます。

関数が引数として、関数ポインタとクロージャのどちらも受け入れられるように、ジェネリックな型とクロージャトレイトの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」を参照してください。



第19章 第5節は以上です!