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()  // panic on possible file-reading errors
                   // ファイル読み込みエラーの場合はパニックする。
        .lines()  // split the string into an iterator of string slices
                  // 文字列のスライスのイテレータに分割する。
        .map(String::from)  // make each slice into a string
                            // スライスを文字列に変換する。
        .collect()  // gather them together into a vector
                    // ベクタにまとめる。
}
}

上の例では、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() {
    // File hosts.txt must exist in the current path
    // hosts.txtファイルは現在のパスに存在しなければならない。
    if let Ok(lines) = read_lines("./hosts.txt") {
        // Consumes the iterator, returns an (Optional) String
        // イテレータを消費し、Option型のStringを返す。
        for line in lines {
            if let Ok(ip) = line {
                println!("{}", ip);
            }
        }
    }
}

// The output is wrapped in a Result to allow matching on errors
// Returns an Iterator to the Reader of the lines of the file.
// 出力は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を作ると、より大きなファイルを取り扱う際に、パフォーマンスの問題につながります。