第27回です。前回はこちら。
[第27回の様子]
2022/02/02に第27回を開催した。
第27回の内容としてはRust By Example 日本語版の「9.2.3. クロージャを受け取る関数」に取り組んだ。
参加者は8人。安定して5人以上参加者がいて嬉しい😊
今日はSlackでのいい感じの感想があったので久しぶりに掲載。
[学んだこと]
- 9.2.3. クロージャを受け取る関数
- クロージャの話の続き。むしろ前回の方が難しかったような...?
- 引数でクロージャを受け取る場合はジェネリック型でないといけないらしい
// `F` はジェネリック型でなくてはならない fn apply<F>(f: F) where F: FnOnce() { f(); } // 以下の書き方だとコンパイルエラー fn apply<Fn>(f: Fn) { f(); } // コンパイルエラー詳細 error[E0618]: expected function, found `Fn` --> src/main.rs:4:5 | 3 | fn apply<Fn>(f: Fn) { | - `f` has type `Fn` 4 | f(); | ^-- | | | call expression requires function For more information about this error, try `rustc --explain E0618`.
- クロージャが定義されると、コンパイラは裏側で、無名の構造体を作り、外側の変数を入れ、Fn、FnMut、FnOnceいずれか一つを実装する、らしい
- コンパイラそんな複雑なことやってたのね...
- このクロージャの構造体が無名で型が不明なので、クロージャを引数にとる際の型がジェネリクスになる必要がある
- 今日の内容はここまで
- だったのだが、それで終わると面白くないので、実際に構造体を作り、Fnを実装してみることにした
- まずは構造体を定義する
// 無名ではない構造体 struct PrintFn { x: i32 }
- 適当にFnを実装してみる
impl Fn for PrintFn { fn call(&self) { println!("{}", self.x) } } // 以下のコンパイルエラー error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change --> src/main.rs:31:6 | 31 | impl Fn for PrintFn { | ^^ help: use parenthetical notation instead: `Fn() -> ()` | = note: see issue #29625 <https://github.com/rust-lang/rust/issues/29625> for more information error[E0107]: missing generics for trait `Fn` --> src/main.rs:31:6 | 31 | impl Fn for PrintFn { | ^^ expected 1 generic argument | help: add missing generic argument | 31 | impl Fnfor PrintFn { | ~~~~~~~~ error[E0658]: use of unstable library feature 'fn_traits' --> src/main.rs:32:5 | 32 | fn call(&self) { println!("{}", self.x) } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: see issue #29625 <https://github.com/rust-lang/rust/issues/29625> for more information
- 大量のコンパイルエラーに悩まされながら、ワイワイやって、なんとか以下の形に落ち着いた
#![feature(unboxed_closures)] #![feature(fn_traits)] // 引数の型をUnit(())にする必要があった // FnOnce, FnMut, Fnは同時に宣言する必要がある // それぞれに対応するcallを宣言する impl FnOnce<()> for PrintFn { type Output = (); extern "rust-call" fn call_once(self, args: ()) { println!("{}", self.x) } } impl FnMut<()> for PrintFn { extern "rust-call" fn call_mut(&mut self, args: ()) { println!("{}", self.x) } } impl Fn<()> for PrintFn { extern "rust-call" fn call(&self, args: ()) { println!("{}", self.x) } }
- こうして定義したPrintFnを以下のように利用する
fn apply<F>(f: F) where F: Fn() { f(); } fn main() { let x = 7; // クロージャを利用する let print = || println!("{}", x); apply(print); // 7が出力される // 無名ではない構造体`PrintFn`を宣言し、`printFn`にアサインする。 let printFn = PrintFn{x}; apply(printFn); // 7が出力される }
- 時間はかかったけど、コンパイラがクロージャに大してどういう処理を実行しているか理解が深まった気がする。
- そもそもFnトレイトのみを実装した場合、以下のようにコンパイルエラーを出してクロージャを使うように進めてくれるので、本当にRustのコンパイラ賢い
error[E0277]: expected a `FnMut<()>` closure, found `PrintFn` --> src/main.rs:30:6 | 30 | impl Fn<()> for PrintFn { | ^^^^^^ expected an `FnMut<()>` closure, found `PrintFn` | = help: the trait `FnMut<()>` is not implemented for `PrintFn` = note: wrap the `PrintFn` in a closure with no arguments: `|| { /* code */ }` note: required by a bound in `Fn`
[まとめ]
モブプログラミングスタイルでRust dojoを開催した。
Fnを自前で実装してみて、クロージャの便利さがよくわかった。
今週のプルリクエストはこちら。