にたまごほうれん草ブログ

はてなダイアリーから移行したブログ。以前のはこちら→http://d.hatena.ne.jp/emergent/

Rustコンパイラの優しさに触れながらフィボナッチ関数のクロージャを作る

きっかけ

A Tour of Goを見てたら、クロージャを用いてフィボナッチ数列を出力する例題がありました。 fibonacci()関数の中は自分で作成する必要がある問題だったのですが、解答としてはおそらくこんな感じ。

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    x := 0
    y := 1
    return func() int {
        ans := x
        x, y = y, x+y
        return ans
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

なんとなくPythonのジェネレータとも近い雰囲気を感じましたが、こちらはイテレータオブジェクトを生成するのでちょっと違いますね。Pythonyieldを使って書いたのがこんな感じ。

def fib(i):
    x = 0
    y = 1
    for _ in range(i):
        ans = x
        x, y = y, x+y
        yield ans


for v in fib(10):
    print(v)

Rustで書く

さて、そういえばRustでこういうの書いた覚えがないなーと思って挑戦してみました。最終的にはこうなりました。

fn fibonacci() -> impl FnMut() -> i32 {
    let mut x = 0;
    let mut y = 1;
    move || {
        let ans = x;
        let tmp = x;
        x = y;
        y += tmp;
        ans
    }
}

fn main() {
    let mut f = fibonacci();
    for _ in 0..10 {
        println!("{}", f());
    }
}

最初は、たぶんコンパイラに怒られるだろうと、fn fibonacci() -> Fn() -> i32 なんてシグネチャで書いてみたのですが案の定怒られまして。だけどコンパイラさんが「dynつけろ」「FnMutにしろ」とか丁寧に指示してくれるので、一旦は以下のような形で動作させることはできました。

fn fibonacci() -> Box<dyn FnMut() -> i32> {
    // ...

    Box::new(move || {
        // ...
    }
}

でも、Box使うのなんかやだなーと思って外してみたら「impl Trait使えばええんやで」とコンパイラさんがまたしても教えてくれたので上記のような形になりました。

↓温かいアドバイス

  = note: for information on `impl Trait`, see <https://doc.rust-lang.org/book/ch10-02-traits.html#returning-types-that-implement-traits>
help: use `impl FnMut() -> i32` as the return type, as all return paths are of type `[closure@src/main.rs:4:5: 10:6]`, which implements `FnMut() -> i32`
  |
1 | fn fibonacci() -> impl FnMut() -> i32 {
  |                   ^^^^^^^^^^^^^^^^^^^

Rustコンパイラさんの優しさに涙が出そうになった夜でした。これからも推し続けます。