web-dev-qa-db-ja.com

ファイルを読み取り、文字列の配列を取得する

ファイルを読み取ってStringsのベクターを取得したい。次の関数は機能しますが、より簡潔または慣用的な方法はありますか?

use std::fs::File;
use std::io::Read;

fn lines_from_file(filename: &str) -> Vec<String> {
    let mut file = match File::open(filename) {
        Ok(file) => file,
        Err(_) => panic!("no such file"),
    };
    let mut file_contents = String::new();
    file.read_to_string(&mut file_contents)
        .ok()
        .expect("failed to read!");
    let lines: Vec<String> = file_contents.split("\n")
        .map(|s: &str| s.to_string())
        .collect();
    lines
}

私にとって次善のように思われるいくつかのこと:

  • ファイルを読み取るための2つの個別のエラーチェック。
  • ファイル全体をStringに読み取ると、破棄されます。最初のN行だけが必要な場合、これは特に無駄になります。
  • &str 1行あたり。何とかしてファイルからString 1行あたりに直接移動するのではなく、破棄されます。

これをどのように改善できますか?

22
Nathan Long

DK。の答え は非常に正確であり、すばらしい説明があります。ただし、次のように述べています。

ファイルを読み取り、文字列の配列を取得する

Rust配列は固定長であり、コンパイル時に認識されるため、「ベクトル」を意味していると思います。私はそれを次のように書きます:

use std::{
    fs::File,
    io::{prelude::*, BufReader},
    path::Path,
};

fn lines_from_file(filename: impl AsRef<Path>) -> Vec<String> {
    let file = File::open(filename).expect("no such file");
    let buf = BufReader::new(file);
    buf.lines()
        .map(|l| l.expect("Could not parse line"))
        .collect()
}

// ---

fn main() {
    let lines = lines_from_file("/etc/hosts");
    for line in lines {
        println!("{:?}", line);
    }
}
  1. 他の回答と同様に、ファイル名にAsRefを実装するジェネリック型を使用することは価値があります。
  2. Result::expectErrのパニックを短縮します。
  3. BufRead::lines は、"\n"だけでなく、複数の種類の改行を処理します。
  4. BufRead::linesは、1つの大きなグロブの代わりに、個別に割り当てられたStringsも提供します。
  5. それを返すためだけに一時変数に収集する理由はありません。タイプを繰り返す理由は特にありません(Vec<String>)。

失敗時にResultを返したい場合、必要に応じて実装を1行に縮小できます。

use std::{
    fs::File,
    io::{self, BufRead, BufReader},
    path::Path,
};

fn lines_from_file(filename: impl AsRef<Path>) -> io::Result<Vec<String>> {
    BufReader::new(File::open(filename)?).lines().collect()
}

// ---

fn main() {
    let lines = lines_from_file("/etc/hosts").expect("Could not load lines");
    for line in lines {
        println!("{:?}", line);
    }
}
18
Shepmaster

BurntSushiが言った のように、あなたは単に the lines() iterator を使用できます。ただし、質問に現状のまま対処するには:

  • おそらく Rustでのエラー処理 ;を読んでください。これらのunwrap() sは_?_ sに変換する必要があります。関数の結果は、合理的なEの場合は_Result<Vec<String>, E>_になります。ここでは、_io::Result_タイプのエイリアスを再利用しています。

  • lines()イテレータを使用します。他にできることは、ファイル全体をStringに読み込み、それを返すことです; a lines()文字列のイテレータ があります。

  • これはあなたが何もすることはできません:_file_contents_はその内容を所有し、それらを複数の所有されたStringsに分割することはできません。あなたができる唯一のことは、各行の内容を借りて、それを新しいStringに変換することです。とはいえ、これを表現する方法は、_&str_の作成にはコストがかかると考えていることを意味します。そうではありません。これはliterallyオフセットのペアを計算してそれらを返すだけです。 _&str_スライスは、実質的に_(*const u8, usize)_と同等です。

基本的に同じことを行う修正バージョンは次のとおりです。

_use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

fn lines_from_file<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())
}
_

私が行ったもう1つの変更:filenameが一般的な_P: AsRef<Path>_になりました。これは _File::open_ が必要とするものであり、変換を必要とせずにより多くの型を受け入れるためです。

16
DK.