0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rust the book 要約 19章 ー4節:高度な機能 ー 関数とクロージャについての発展的な事項

Posted at

目次

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::ValueStatus 型の初期化関数になることを表す

  • この初期化関数も関数ポインタとして使用できる

  • 例:

    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 節で述べたように動的サイズ決定型を使用する際は、ポインタと組み合わせる必要がある

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?