Rustでcatコマンドを書く
catと言っても簡易的なもので、与えられた引数のファイルの中身を表示するだけで、オプションとかそんなものはありません。
Rustの勉強用に書いてみました。
というわけで早速コード。
use std::env; use std::fs::File; use std::io::{BufReader, BufRead}; fn main() { // コマンドライン引数の取得 let f_name = env::args().nth(1).unwrap(); // 引数のファイル名を受け取りファイルを開く let f = File::open(f_name).unwrap(); // バッファリングのためにBufReaderを使う let f = BufReader::new(f); // 一行ずつ取得して出力 for line in f.lines() { println!("{}", line.unwrap()); } }
test.txt
とか適当なファイルを用意して、実行してみます。
$ rustc cat.rs $ ./cat test.txt hello world
できてますね。
ところで、コードの中に時々出てくるunwrap()
とは何でしょう。
unwrap()
は列挙型の1つであるOption<T>
型やResult<T,E>
型の値から、T
の値を取得するために使われます。
「Optional<T>
型やResult<T,E>
型?よくわからないけど何でそんなん使うの?unwrap()
使わずに初めから値を返してくれればよくない?」と、思うかもしれませんが、これはエラーハンドリングにおいて重要な役割を果たしています。
例えば、先ほどの簡易catに、引数を与えず実行してみます。
$ ./cat thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', libcore/option.rs:345:21 note: Run with `RUST_BACKTRACE=1` for a backtrace.
はい、エラーが表示されました。
このとき、もう少し親切なエラーメッセージを表示したいとなった場合、どうすれば良いでしょう?
これは、引数に与えたファイル名が存在しなかった場合も同様です。
「例外処理で書けばいいのかなー」と思うかもしれません。
実は、Rustにはいわゆる例外の仕組みが存在せず、エラーも戻り値で表します。
なので、catch
してthrow
する、みたいなことはできません。
そこで、戻り値をOption<T>
型やResult<T,E>
型で返し、それらをmatch
式と呼ばれるものと組み合わせて、エラーハンドリングを実現します。
具体的には以下のような記述になります。
// Option<T>型の場合 match Option<T> { Some(T) => { // 値があれば処理を続行 }, None => { // 値がなければエラー処理 } } // Result<T,E>型の場合 match Result<T, E> { Ok(T) => { // 値があれば処理を続行 }, Err(E) => { // 値がなければエラー処理 } }
では早速、簡易catのmain()
を書き直してみましょう。
fn main() { match env::args().nth(1) { Some(f_name) => { match File::open(f_name) { Ok(f) => { for line in BufReader::new(f).lines() { match line { Ok(l) => println!("{}", l), Err(e) => { println!("Error: Cannot read a line. {}", e); return; } } } } Err(e) => { println!("Error: Cannot open the file. {}", e); return; } } }, None => { println!("Error: No args."); return; } } }
$ rustc cat.rs $ ./cat test.txt hello world
一応、できてますね。引数を与えずに実行してみます。
$ ./cat Error: No args.
こちらで用意したエラーメッセージが表示されました。
存在しないファイル名も渡してみます。
$ ./cat foo Error: Cannot open the file. No such file or directory (os error 2)
いい感じ。
このように、unwrap()
を使えば簡単に値を取得できるのですが、基本的にはunwrap()
を使わず、適切なエラー処理を記述していくのが良いですね。
それにしても、これ、ネスト深くなりすぎてますよね。。。
書き換えましょう。
fn main() { let f_name = match env::args().nth(1) { Some(f_name) => f_name, None => { println!("Error: No args."); return; } }; let f = match File::open(f_name) { Ok(f) => f, Err(e) => { println!("Error: Cannot open the file. {}", e); return; } }; let reader = BufReader::new(f); for line in reader.lines() { let l = match line { Ok(l) => l, Err(e) => { println!("Error: Cannot read a line. {}", e); return; } }; println!("{}", l); } }
少しマシになりました。
というわけで、今後もたまにRust書きます。