Rust dojo第27回を開催した

第27回です。前回はこちら。

kdnakt.hatenablog.com

 

 

[第27回の様子]

2022/02/02に第27回を開催した。

 

第27回の内容としてはRust By Example 日本語版の「9.2.3. クロージャを受け取る関数」に取り組んだ。

 

参加者は8人。安定して5人以上参加者がいて嬉しい😊

 

今日はSlackでのいい感じの感想があったので久しぶりに掲載。

f:id:kidani_a:20220204102004p:plain

f:id:kidani_a:20220204101811p:plain

f:id:kidani_a:20220204101709p:plain

 

[学んだこと]

// `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 Fn for 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が出力される   
}
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を自前で実装してみて、クロージャの便利さがよくわかった。

 

今週のプルリクエストはこちら。

github.com