Rust で brainfuck を書いてみての感じ

Rust は完全に趣味で型のある言語触っておくかというモチベーションでやっているだけのものなので極めたいとかは全くないのだけど動くものをつくらないとダサいという思いがあるので入門に最適な brainfuck を書いた。 https://github.com/mtwtkman/bf/blob/master/rust/src/main.rs

色々書き方はあるのだけど今回はちゃんと tokenize と [ ]マッピングまで行った。実装を行う中でいくつかのトピックごとに所感を書き記しておく。

まず初めに思うことは型の定義は面倒ではあるがそれを補って余りあるほどの安心感が確実にある。今回のような遊び目的だとメリットは薄めだがチーム開発で長期運用という前提であればこれほど頼りになるものはないと改めて実感した。 今回の実装方法でいえば tokenize の際に定義した enum によりコードがたちまち書き易くなった。

#[derive(Debug, Clone, Copy, PartialEq)]
enum Token {
    Inc,
    Dec,
    Next,
    Prev,
    GetChar,
    PutChar,
    Open,
    Close,
}

impl Token {
    fn tokenize(ch: &char) -> Result<Self, BfError> {
        match ch {
            '+' => Ok(Token::Inc),
            '-' => Ok(Token::Dec),
            '>' => Ok(Token::Next),
            '<' => Ok(Token::Prev),
            ',' => Ok(Token::GetChar),
            '.' => Ok(Token::PutChar),
            '[' => Ok(Token::Open),
            ']' => Ok(Token::Close),
            _ => Err(BfError::TokenizeError),
        }
    }
}

この Token 型で宣言的な evaluator の実装が進められた。また、 Result 型もすばらしく、コンビネーターを使って効果的に異常処理のハンドリングを定義することもできるのでその書き易さからいい加減で煩雑になりがちな条件分岐や例外処理から少しは解放されると感じた。

異常処理

Rust は言語仕様として未定義状態の処理を放置しづらくなっている。とりあえず unwrap を使うことで異常処理が発生しうるマークになるし、ResultOption によるコンビネーターを用いての実装は言わずもがなだ。 まだ使う場面にたちあったことはないが unsafe キーワードでその辺はよしなにできるようだが基本的に unsafe であることが明示されているので無意識のうちにバギーな箇所を見逃す、または見落とすということも少ないだろう。 特に、これまで Python ばかり触ってきた身としてはこの異常処理がそれなりに快感になる。 ある程度の強制力が働いているので精神衛生上、ポジティブな影響を与えているのだろうと感じている。

末尾再帰

全く調べてないので適当ではあるが Rust はどうやら愚直に書くと末尾再帰の最適化がされないようだ。 試しにフィボナッチ関数を末尾再帰で書いてみたが一定の深さで overflow して panic を起こす。 https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=37670a0a3483f8d7e3d1d10eae497b50

結局 Iterator として処理することにしたが再帰処理で書いた方が mutable の変数をなくせるなどそれなりにメリットとおもえる場合もあるので是非とも使いたい気持ちは残る。

Scala であれば末尾再帰の最適化がされるし、それを担保する専用のアノテーションがあるので是非 Rust にもほしい。 https://scastie.scala-lang.org/4urTHWxZRxKIKYqCCrHNIw

テスト

特段テストが書き易いわけでもないがテストを始める準備が全くいらず #[test] というアトリビュートをつければいいだけなのでとてもハンディだと思う。 ほぼアプリケーションコードと同じ書き味であるしアサーションも何の変哲もないマクロだしこれと言って感じることはなかった。 しかしながらテストを書き始めるのに何も準備がいらないというのはとても大事なことなのですごく好感をもてた。 といっても近頃の言語なら処理系標準のテストランナーはあるだろうけど。

コンパイル

型に関する余計なテストを書かなくてすむというのは本当にでかい。 特に値の有無だったり入力値の型だったりと動的型付けの言語で常にテスト対象となりうる部分がコンパイルで担保されるのは大きなメリットに感じる。