read lines
単純なやり方
テキストファイルの行を読み込むのを、初心者が初めて実装した場合、以下のようになるでしょう。
#![allow(unused)]
fn main() {
use std::fs::read_to_string;
fn read_lines(filename: &str) -> Vec<String> {
let mut result = Vec::new();
for line in read_to_string(filename).unwrap().lines() {
result.push(line.to_string())
}
result
}
}
lines()メソッドはファイルの各行のイテレータを返すので、インラインでマップを実行し結果を収集することもできます。そうすると、より簡潔で読みやすい表現となります。
#![allow(unused)]
fn main() {
use std::fs::read_to_string;
fn read_lines(filename: &str) -> Vec<String> {
read_to_string(filename)
.unwrap() // ファイル読み込みエラーの場合はパニックします。
.lines() // 文字列のスライスのイテレータに分割します。
.map(String::from) // スライスを文字列に変換します。
.collect() // ベクタにまとめます。
}
}
上の例では、lines()から返された&strをそれぞれto_string()とString::fromを使って、所有権のあるString型に変換しなければならない点に注意してください。
より効率的なやり方
ここでは、開いたFileの所有権をBufReader構造体に渡します。BufReaderは内部的なバッファを使い、中間のメモリ割り当てを削減します。
read_linesを更新して、それぞれの行に対してメモリ上に新しいStringオブジェクトを割り当てるのではなく、イテレータを返すようにします。
use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;
fn main() {
// hosts.txtファイルは現在のパスに存在しなければなりません。
if let Ok(lines) = read_lines("./hosts.txt") {
// イテレータを消費し、Option型のStringを返します。
for line in lines.map_while(Result::ok) {
println!("{}", line);
}
}
}
// 出力はResult型にラップされ、エラーをマッチできるようになります。
// ファイルの各行のReaderへのイテレータを返します。
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where P: AsRef<Path>, {
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
このプログラムを実行すると、単に各行を出力します。
$ echo -e "127.0.0.1\n192.168.0.1\n" > hosts.txt
$ rustc read_lines.rs && ./read_lines
127.0.0.1
192.168.0.1
File::openはジェネリックなAsRef<Path>を引数にとるので、ジェネリックなread_linesメソッドも、whereキーワードを使って、同じジェネリックな制約を持たせています。
この処理は、ファイルの中身全てをメモリ上のStringにするよりも効率的です。メモリ上にStringを作ると、より大きなファイルを取り扱う際に、パフォーマンスの問題につながります。