6

ファイルを開いて内容を 1 行ずつ検索し、一致する各行に対して何かを実行する大きなコード ブロックがありました。これを、ファイルへのパスを取得して一致する行を提供する独自の関数に分解したいのですが、これを正しく分解する方法がわかりません。

これは私が近いと思うものですが、コンパイルエラーが発生します:

/// get matching lines from a path
fn matching_lines(p: PathBuf, pattern: &Regex) ->  Vec<String> {
    let mut buffer = String::new();
    // TODO: maybe move this side effect out, hand it a
    //       stream of lines or otherwise opened file
    let mut f = File::open(&p).unwrap();
    match f.read_to_string(&mut buffer) {
        Ok(yay_read) => yay_read,
        Err(_) => 0,
    };
    let m_lines: Vec<String> = buffer.lines()
        .filter(|&x| pattern.is_match(x)).collect();
    return m_lines;
}

そしてコンパイラエラー:

src/main.rs:109:43: 109:52 error: the trait `core::iter::FromIterator<&str>` is not implemented for the type `collections::vec::Vec<collections::string::String>` [E0277]
src/main.rs:109         .filter(|&x| pattern.is_match(x)).collect();
                                                          ^~~~~~~~~
src/main.rs:109:43: 109:52 help: run `rustc --explain E0277` to see a detailed explanation
src/main.rs:109:43: 109:52 note: a collection of type `collections::vec::Vec<collections::string::String>` cannot be built from an iterator over elements of type `&str`
src/main.rs:109         .filter(|&x| pattern.is_match(x)).collect();
                                                          ^~~~~~~~~
error: aborting due to previous error

String代わりに使用すると、&str代わりにこのエラーが発生します。

src/main.rs:108:30: 108:36 error: `buffer` does not live long enough
src/main.rs:108     let m_lines: Vec<&str> = buffer.lines()
                                             ^~~~~~

どのような意味がありますか。行は関数の最後で範囲外になる内にとどまると思うbufferので、文字列への参照のベクトルを収集してもあまり役に立ちません。

行のコレクションを返すにはどうすればよいですか?

4

2 に答える 2

7

Rust Playgroundで実行されるこのバージョンから始めましょう(質問するときにMCVEを作成することをお勧めします)。

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

fn matching_lines(p: PathBuf, pattern: &str) -> Vec<String> {
    let mut buffer = String::new();
    let mut f = File::open(&p).unwrap();
    match f.read_to_string(&mut buffer) {
        Ok(yay_read) => yay_read,
        Err(_) => 0,
    };
    let m_lines: Vec<String> = buffer.lines()
        .filter(|&x| x.contains(pattern)).collect();
    return m_lines;
}

fn main() {
    let path = PathBuf::from("/etc/hosts");
    let lines = matching_lines(path, "local");    
}

の署名を見てみましょうstr::lines:

fn lines(&self) -> Lines // with lifetime elision
fn lines<'a>(&'a self) -> Lines<'a> // without

最初にソースでどのように見えるかを示し、次にそれを精神的に翻訳できるものを示しました。String読んだによって裏付けられた文字列スライスのイテレータを返します。これは、割り当てが 1 つだけで済むため、非常に効率的であるため、優れた機能です。ただし、所有する値とその値への参照を同時に返すことはできません。最も簡単な方法は、 Benjamin Lindleyが提案するように、各行を所有された文字列に変換することです。

let m_lines: Vec<String> =
    buffer
    .lines()
    .filter(|&x| x.contains(pattern))
    .map(ToOwned::to_owned)
    .collect();

これでコードがコンパイルされますが、さらに良くすることができます。ステートメントmatchは に置き換えることができunwrap_orますが、エラーのケースを完全に無視しているため、次のように使用することもできます_

let _ = f.read_to_string(&mut buffer);

これは本当に良い考えではないことに注意してください。エラーは報告することが重要であり、最も報告が必要なときにエラーを投げ捨てることはあなたを悩ませます! unwrapエラーが発生したときにプログラムを終了させて​​使用する方がおそらく安全です。

次に、明示的なreturnステートメントを使用しないでください。必要でない限り、型注釈を提供しないでください。関数は を返すVec<String>ため、最後の 2 行を次のように置き換えることができます。

buffer
    .lines()
    .filter(|&x| x.contains(pattern))
    .map(ToOwned::to_owned)
    .collect()

また、何をサポートするpかをよりよく一致させるために、 に対して受け入れるタイプについてよりオープンにすることもできます。File::open

fn matching_lines<P>(p: P, pattern: &str) -> Vec<String>
    where P: AsRef<Path>

すべて一緒に:

use std::path::{Path, PathBuf};
use std::fs::File;
use std::io::Read;

fn matching_lines<P>(p: P, pattern: &str) -> Vec<String>
    where P: AsRef<Path>
{
    let mut buffer = String::new();
    let mut f = File::open(p).unwrap();
    let _ = f.read_to_string(&mut buffer);

    buffer
        .lines()
        .filter(|&x| x.contains(pattern))
        .map(ToOwned::to_owned)
        .collect()
}

fn main() {
    let path = PathBuf::from("/etc/hosts");
    let lines = matching_lines(path, "local");
    println!("{:?}", lines);
}
于 2015-09-28T14:30:03.150 に答える