PNGを端末に表示するプログラムをRustで書いた

PNGを端末に表示するプログラムを作りました。

github.com

使い方

cargo run /Path/to/Windows_logo.png

とすると、👇のように端末に画像が表示されます。透過画像も表示できます。

ちなみに、PNGの画像形式は何パターンかあるのですが、そのすべてに対応してるわけではないので、表示できない画像もあります。あしからず。

メイキング

PNGの規格が理解できたら、あとは流れで作れると思うので、PNGの規格を理解しましょう。 PNGの規格はRFCで公開されているので、それを読んでください。

...というわけにも行かないので、解説記事を読んで下さい 👇

dawn.hateblo.jp

とはいえ、RFCの英語は簡単だし読みやすいので、読むのもそこまで大変ではないと思います。

なぜやったのか

mixiインターン期間中に開催された「インターン生vs社員LT大会」で、インターン生の人がzip圧縮に関する発表をしていました。その人は、zipの規格を読んでそれを元にzip圧縮するソフトをC++で実装したそうです。
なんでわざわざC++でやったんですか?とその人に聞いたところ、バイト列の扱いがLLなどに比べてやりやすいからだという返事が返ってきました。

なるほどな〜やっぱり人間はC++を書かなきゃいけないんやな〜ってその時は思ったんですが、インターンが終わるぐらいのときに、Rustはそういう低レイヤーなものを書くのに向いているのではないか、と思うようになりました。

そういうお気持ちだったときに、「ディジタル情報処理」という授業の先生が「ディジタル情報処理に関係するプログラム書いたら単位出すで」と言っていたので、いっちょうやったるかという機運が高まり、プログラムが作成されました*1*2

Rustに関する感想

バイト列の取り扱いはいい感じだった

結論から言うと、Rustでバイト列を取り扱うのは結構やりやすいです*3

例えば、u8の配列から4つづつ読み出して、u32と見做して値を取得したいというような課題があるわけです。 そういうとき、Rustにはbyteorderというcrateがあって、👇のようにして数値に変換できます。

extern crate byteorder;
use byteorder::{BigEndian, ReadBytesExt};

fn main() {
    let bytes: Vec<u8> = vec![0,0,1,1, 0,0,1,2];

    println!("{}", (&bytes[0..4]).read_u32::<BigEndian>().unwrap()); // => 257
    println!("{}", (&bytes[4..8]).read_u32::<BigEndian>().unwrap()); // => 258
}

こういうcrateがあって、そこそこ人気がある(githubで200スターぐらい)っていうことは、低レイヤーな処理をRustにやらせたいという需要はやはり一定あるのかな、と感じます。

他にも、Rustにはプリミティブな数値型がたくさんあって、用途に応じて使い分けられるのは良いと思いました。

また、オーバーフローが起きたときにエラーを出してくれるのですが、各数値型にはwrapping_addのようなメソッドが生えておりまして、このメソッドを通して計算をすると、オーバーフローが起こったときにスルーしてくれます。こういうのが明示的に書けるのは良いですね。

fn main() {
  let a: u8 = 200;
  let b: u8 = 200;
  
  let c: u8 = a + b;             // => panic!
  let c: u8 = a.wrapping_add(b); // => 144
}

Rustの学習コストはやっぱり高い

一方で、やはりRustの学習コストは高いなぁと思いました。 僕自身はライフタイムや型システムを(一応)理解しているので、謎のコンパイルエラーに悩まされるという初心者あるある現象が発生することはなかったですが、moveを避けるために.clone()を乱発するコードになってしまい、メモリをうまく使えていないなあという気持ちになりました。

まとめ

車輪の再発明を通して学んでいきたい。

github.com

*1:ちなみにこのプログラムを先生に見せたら「ディジタル情報処理に関係してるかっていうと微妙だねぇ」という返答が返ってきました。先生的には離散フーリエ変換とかしてほしかったみたいです。

*2:エッジの検出してAAに変換するとかすれば、ディジタル情報処理とみなせると言われました。次の課題はこれです。

*3:僕はバイト列をゴニョゴニョするようなことは普段やらないし、ましてやC++なんて書いたこともないので、あくまで「LLに比べて」やりやすいということです