時折思い出したようにRustの勉強をしています。「しています」と書くとかっこいいですが、一向に最初の一歩から進みません。
そこで方針を変えて、少しだけ現実的なプログラムを組んでみることにしました。名著『ソフトウェア作法』で最初に紹介されているcopyプログラムです。このプログラムは標準入力からの入力を標準出力に出力するだけのプログラムです。
同書で使用されているのは改造FORTRAN言語RATFOR(RATional FORtran)です。RATFORによるcopyプログラムはこんな具合です(”¬”は論理否定)。
integer getc integer c while ( getc(c) ¬= EOF ) call putc(c) stop end
大学生の時にこの本を読んでプログラミングの基礎を学びました。RATFORによる例題はUnix上で動作することを前提に開発されており、RATFOR自身もFORTRANベースとはいえC言語に似ています。おかげでC言語が使えるようになった時にも書き方で苦労することはありませんでした。
さて、同じ調子でRustも…と思ったのですがこれが難しい。このブログエントリには何事もなかったような顔で書いていますが、入出力の設計思想がC言語の時代と全く違うことに七転八倒しました。
古いタイプの言語ではFILE型変数に標準入力の状態が記録してあり、この変数に対して「1行読み出し関数」を用いることで、1行のデータを取り出していました。Rustでも同じ調子だろうと調べてみましたが、話がかみ合いません。
Rustでは、リーダー変数を標準入力に結び付けて作ります。ここまでは古い言語と似ていますが、次のステップではだいぶ違います。リーダー変数からlines()メソッドを使って次に読む行へのイテレーターを取り出すのです。
このイテレータを使って行データを取り出し、標準出力に出力します。わざわざイテレータを使わずとも、と言う気がしますが、イテレータを使うことでfor … in 構文を利用することができ、配列やベクトルへのアクセスと同じ考え方でプログラムを書くことができます。
// Copy the Standard input stream to the Standard output stream. use std::io::{stdin, BufRead, BufReader}; fn main() { // Create a reader variable, tied with the standard input. let reader = BufReader::new(stdin()); // For each lines of the standard input: for line in reader.lines() { // print that line to the standard output. // line iterator returns the Result type. So, wo need to unwrap. println!("{}", line.unwrap()); } }
いやはや。あまりにも長い時間、ベアメタルにばかりかまけて勉強を怠ったツケをいっぺんに支払う羽目になったようです。
とはいえ、Rustの標準入力の方法について検索すると、ほとんどの人が「1行入力して1行出力するだけ」のプログラムについて解説しています。Rustの方法が多くのプログラマにとってなじみ深いのかどうか、今のところは意見を保留しておきます。
最後に、今回の勉強結果はGitHubのリポジトリとして公開しています。